@playcanvas/web-components 0.10.0 → 0.11.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/dist/app.d.ts +151 -35
- package/dist/asset.d.ts +9 -0
- package/dist/async-element.d.ts +12 -2
- package/dist/components/camera-component.d.ts +13 -13
- package/dist/components/component.d.ts +5 -4
- package/dist/components/screen-component.d.ts +25 -4
- package/dist/components/scrollview-component.d.ts +6 -4
- package/dist/custom-elements.json +812 -196
- package/dist/entity.d.ts +18 -4
- package/dist/loading-bar.d.ts +35 -0
- package/dist/material.d.ts +8 -2
- package/dist/parse.d.ts +4 -2
- package/dist/pwc.cjs +661 -229
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +661 -229
- 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 +662 -230
- package/dist/pwc.mjs.map +1 -1
- package/dist/scene.d.ts +11 -3
- package/dist/sky.d.ts +12 -11
- package/dist/vscode.html-custom-data.json +47 -22
- package/dist/web-types.json +384 -121
- package/package.json +1 -1
- package/src/app.ts +333 -124
- package/src/asset.ts +25 -0
- package/src/async-element.ts +14 -4
- package/src/components/camera-component.ts +31 -26
- package/src/components/component.ts +6 -5
- package/src/components/screen-component.ts +42 -12
- package/src/components/scrollview-component.ts +6 -4
- package/src/entity.ts +61 -27
- package/src/loading-bar.ts +122 -0
- package/src/material.ts +10 -4
- package/src/parse.ts +5 -3
- package/src/scene.ts +49 -19
- package/src/sky.ts +26 -25
package/dist/pwc.js
CHANGED
|
@@ -20,11 +20,21 @@
|
|
|
20
20
|
this._readyResolve = resolve;
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* The nearest ancestor `<pc-app>` element, or `null` if this element has no `<pc-app>`
|
|
25
|
+
* ancestor. The search starts at the parent, so an element never resolves to itself.
|
|
26
|
+
* @returns The closest app element, or `null`.
|
|
27
|
+
*/
|
|
23
28
|
get closestApp() {
|
|
24
|
-
return this.parentElement?.closest('pc-app');
|
|
29
|
+
return this.parentElement?.closest('pc-app') ?? null;
|
|
25
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The nearest ancestor `<pc-entity>` element, or `null` if this element has no `<pc-entity>`
|
|
33
|
+
* ancestor. The search starts at the parent, so an element never resolves to itself.
|
|
34
|
+
* @returns The closest entity element, or `null`.
|
|
35
|
+
*/
|
|
26
36
|
get closestEntity() {
|
|
27
|
-
return this.parentElement?.closest('pc-entity');
|
|
37
|
+
return this.parentElement?.closest('pc-entity') ?? null;
|
|
28
38
|
}
|
|
29
39
|
/**
|
|
30
40
|
* Called when the element is fully initialized and ready. Subclasses should call this when
|
|
@@ -118,6 +128,116 @@
|
|
|
118
128
|
}
|
|
119
129
|
customElements.define('pc-module', ModuleElement);
|
|
120
130
|
|
|
131
|
+
/** Covers the 0.2s opacity transition; jsdom never fires transitionend, so removal is timed. */
|
|
132
|
+
const REMOVAL_DELAY_MS = 250;
|
|
133
|
+
/**
|
|
134
|
+
* The slim progress bar `<pc-app>` shows while it boots and preloads. An implementation detail of
|
|
135
|
+
* AppElement rather than a custom element, so its shape can change without a breaking change.
|
|
136
|
+
*
|
|
137
|
+
* All styling is inline, so the library injects no stylesheet. The colors and height resolve CSS
|
|
138
|
+
* custom properties — `--pc-loading-bar-color`, `--pc-loading-bar-background` and
|
|
139
|
+
* `--pc-loading-bar-height` — so a page can theme the bar from `pc-app` or `:root`.
|
|
140
|
+
*/
|
|
141
|
+
class LoadingBar {
|
|
142
|
+
_track;
|
|
143
|
+
_fill;
|
|
144
|
+
_sweep = null;
|
|
145
|
+
_removal = null;
|
|
146
|
+
/**
|
|
147
|
+
* Creates the bar and appends it to `parent`, starting in the indeterminate state.
|
|
148
|
+
* @param parent - The element to append the bar to.
|
|
149
|
+
*/
|
|
150
|
+
constructor(parent) {
|
|
151
|
+
this._track = document.createElement('div');
|
|
152
|
+
this._track.setAttribute('role', 'progressbar');
|
|
153
|
+
this._track.setAttribute('aria-label', 'Loading');
|
|
154
|
+
this._track.setAttribute('aria-valuemin', '0');
|
|
155
|
+
this._track.setAttribute('aria-valuemax', '100');
|
|
156
|
+
// Fixed positioning matches the canvas, which always fills the window (FILLMODE_FILL_WINDOW)
|
|
157
|
+
this._track.style.cssText = [
|
|
158
|
+
'position: fixed',
|
|
159
|
+
'top: 0',
|
|
160
|
+
'left: 0',
|
|
161
|
+
'width: 100%',
|
|
162
|
+
'height: var(--pc-loading-bar-height, 3px)',
|
|
163
|
+
'background: var(--pc-loading-bar-background, rgba(0, 0, 0, 0.1))',
|
|
164
|
+
'z-index: 10000',
|
|
165
|
+
'pointer-events: none',
|
|
166
|
+
'opacity: 1',
|
|
167
|
+
'transition: opacity 0.2s ease'
|
|
168
|
+
].join('; ');
|
|
169
|
+
this._fill = document.createElement('div');
|
|
170
|
+
this._fill.style.cssText = [
|
|
171
|
+
'width: 100%',
|
|
172
|
+
'height: 100%',
|
|
173
|
+
'transform-origin: left center',
|
|
174
|
+
'transform: scaleX(0)',
|
|
175
|
+
'background: var(--pc-loading-bar-color, #f60)',
|
|
176
|
+
'transition: transform 0.2s ease'
|
|
177
|
+
].join('; ');
|
|
178
|
+
this._track.appendChild(this._fill);
|
|
179
|
+
parent.appendChild(this._track);
|
|
180
|
+
// Indeterminate sweep until the first progress() call reports a real total. No
|
|
181
|
+
// aria-valuenow is set, which is what marks a progressbar indeterminate. jsdom has no Web
|
|
182
|
+
// Animations API, so the guard degrades to a static bar there rather than crashing boot.
|
|
183
|
+
if (typeof this._fill.animate === 'function') {
|
|
184
|
+
this._sweep = this._fill.animate([
|
|
185
|
+
{ transform: 'scaleX(0.25) translateX(-100%)' },
|
|
186
|
+
{ transform: 'scaleX(0.25) translateX(500%)' }
|
|
187
|
+
], {
|
|
188
|
+
duration: 1000,
|
|
189
|
+
iterations: Infinity,
|
|
190
|
+
easing: 'ease-in-out'
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Reflects preload progress, switching the bar from indeterminate to determinate on the first
|
|
196
|
+
* call.
|
|
197
|
+
* @param loaded - The number of assets that have finished loading.
|
|
198
|
+
* @param total - The number of assets being preloaded.
|
|
199
|
+
*/
|
|
200
|
+
progress(loaded, total) {
|
|
201
|
+
if (this._sweep) {
|
|
202
|
+
this._sweep.cancel();
|
|
203
|
+
this._sweep = null;
|
|
204
|
+
}
|
|
205
|
+
const fraction = total === 0 ? 1 : loaded / total;
|
|
206
|
+
this._track.setAttribute('aria-valuenow', String(Math.round(fraction * 100)));
|
|
207
|
+
this._fill.style.transform = `scaleX(${fraction})`;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Fills the bar, fades it out and removes it. Idempotent.
|
|
211
|
+
*/
|
|
212
|
+
complete() {
|
|
213
|
+
if (this._removal !== null) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (this._sweep) {
|
|
217
|
+
this._sweep.cancel();
|
|
218
|
+
this._sweep = null;
|
|
219
|
+
}
|
|
220
|
+
this._track.setAttribute('aria-valuenow', '100');
|
|
221
|
+
this._fill.style.transform = 'scaleX(1)';
|
|
222
|
+
this._track.style.opacity = '0';
|
|
223
|
+
this._removal = setTimeout(() => this._track.remove(), REMOVAL_DELAY_MS);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Removes the bar immediately, cancelling any pending fade. Idempotent.
|
|
227
|
+
*/
|
|
228
|
+
destroy() {
|
|
229
|
+
if (this._sweep) {
|
|
230
|
+
this._sweep.cancel();
|
|
231
|
+
this._sweep = null;
|
|
232
|
+
}
|
|
233
|
+
if (this._removal !== null) {
|
|
234
|
+
clearTimeout(this._removal);
|
|
235
|
+
this._removal = null;
|
|
236
|
+
}
|
|
237
|
+
this._track.remove();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
121
241
|
const CSS_COLORS = {
|
|
122
242
|
aliceblue: '#f0f8ff',
|
|
123
243
|
antiquewhite: '#faebd7',
|
|
@@ -371,7 +491,9 @@
|
|
|
371
491
|
* the value is invalid — the latter also logs a warning listing the valid names.
|
|
372
492
|
*
|
|
373
493
|
* @param value - The attribute value to parse (`null` when the attribute is absent).
|
|
374
|
-
* @param valid - The valid names: an array, or a map whose keys are the valid names.
|
|
494
|
+
* @param valid - The valid names: an array, or a map whose keys are the valid names. Only the keys
|
|
495
|
+
* are read, so the map's value type is unconstrained - engine enums are mostly numeric constants,
|
|
496
|
+
* but some (e.g. `SCALEMODE_BLEND`) are strings.
|
|
375
497
|
* @param defaultValue - The value to use when the attribute is absent or invalid.
|
|
376
498
|
* @param attribute - The attribute name, used in the warning message.
|
|
377
499
|
* @returns The resolved enum name.
|
|
@@ -545,6 +667,11 @@
|
|
|
545
667
|
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.
|
|
546
668
|
* The AppElement interface also inherits the properties and methods of the
|
|
547
669
|
* {@link HTMLElement} interface.
|
|
670
|
+
*
|
|
671
|
+
* @fires {ProgressEvent} progress - Fired while the application preloads its assets. `loaded` and
|
|
672
|
+
* `total` are asset counts, not bytes, and an asset that fails to load still counts as loaded.
|
|
673
|
+
* Fired at least once per boot, and the final event always has `loaded` equal to `total`. Does
|
|
674
|
+
* not bubble.
|
|
548
675
|
*/
|
|
549
676
|
class AppElement extends AsyncElement {
|
|
550
677
|
/**
|
|
@@ -554,10 +681,24 @@
|
|
|
554
681
|
_alpha = true;
|
|
555
682
|
_backend = 'webgpu';
|
|
556
683
|
_antialias = true;
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
684
|
+
_depthBuffer = true;
|
|
685
|
+
_stencilBuffer = true;
|
|
686
|
+
_maxPixelRatio = Infinity;
|
|
687
|
+
_loadingBar = true;
|
|
688
|
+
/**
|
|
689
|
+
* Set once the graphics options above have been handed to `createGraphicsDevice`, after which
|
|
690
|
+
* writing any of them changes nothing. Guards the warning in {@link _warnIfBooted}, and is
|
|
691
|
+
* cleared on disconnect so a re-connected element boots from its current attributes.
|
|
692
|
+
*/
|
|
693
|
+
_optionsLocked = false;
|
|
694
|
+
_bar = null;
|
|
560
695
|
_hierarchyReady = false;
|
|
696
|
+
/**
|
|
697
|
+
* The elements backing this application's entities, keyed by the entity itself. Registered
|
|
698
|
+
* by EntityElement at creation and removed when an entity is destroyed, this joins engine
|
|
699
|
+
* scene nodes back to their owning elements by identity - never by name.
|
|
700
|
+
*/
|
|
701
|
+
_entityElements = new Map();
|
|
561
702
|
_picker = null;
|
|
562
703
|
_hasPointerListeners = {
|
|
563
704
|
pointerenter: false,
|
|
@@ -567,20 +708,34 @@
|
|
|
567
708
|
pointermove: false
|
|
568
709
|
};
|
|
569
710
|
_hoveredEntity = null;
|
|
711
|
+
// Identifies the newest in-flight hover pick, so out-of-order results can be discarded
|
|
712
|
+
_pickToken = 0;
|
|
570
713
|
_pointerHandlers = {
|
|
571
714
|
pointermove: null,
|
|
572
715
|
pointerdown: null,
|
|
573
716
|
pointerup: null
|
|
574
717
|
};
|
|
575
718
|
_app = null;
|
|
719
|
+
_loadProgress = 0;
|
|
576
720
|
/**
|
|
577
|
-
* The PlayCanvas application instance.
|
|
578
|
-
* {@link whenReady} or the element's `ready()`
|
|
579
|
-
*
|
|
721
|
+
* The PlayCanvas application instance. `null` until the element is ready, and again once it
|
|
722
|
+
* has been removed from the document — await {@link whenReady} or the element's `ready()`
|
|
723
|
+
* promise before accessing it.
|
|
724
|
+
* @returns The application instance, or `null`.
|
|
580
725
|
*/
|
|
581
726
|
get app() {
|
|
582
727
|
return this._app;
|
|
583
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* The asset preload progress of the application, as a fraction from 0 to 1. It is 0 until
|
|
731
|
+
* preloading begins (and again once the element has been removed from the document), and 1
|
|
732
|
+
* once preloading has finished — including when there was nothing to preload. Read this to
|
|
733
|
+
* initialize a loading UI; subsequent updates arrive via the `progress` event.
|
|
734
|
+
* @returns The preload progress.
|
|
735
|
+
*/
|
|
736
|
+
get loadProgress() {
|
|
737
|
+
return this._loadProgress;
|
|
738
|
+
}
|
|
584
739
|
/**
|
|
585
740
|
* Creates a new AppElement instance.
|
|
586
741
|
*
|
|
@@ -592,6 +747,11 @@
|
|
|
592
747
|
this._onWindowResize = this._onWindowResize.bind(this);
|
|
593
748
|
}
|
|
594
749
|
async connectedCallback() {
|
|
750
|
+
// Created before the first await, so the bar is visible while modules and the graphics
|
|
751
|
+
// device are created, and exists before any disconnect could need to clean it up
|
|
752
|
+
if (this._loadingBar && !this._bar) {
|
|
753
|
+
this._bar = new LoadingBar(this);
|
|
754
|
+
}
|
|
595
755
|
// Get all pc-module elements that are direct children of the pc-app element
|
|
596
756
|
const moduleElements = this.querySelectorAll(':scope > pc-module');
|
|
597
757
|
// Wait for all modules to load
|
|
@@ -606,15 +766,19 @@
|
|
|
606
766
|
null: ['null']
|
|
607
767
|
};
|
|
608
768
|
const deviceTypes = backendToDeviceTypes[this._backend] || [];
|
|
769
|
+
this._optionsLocked = true;
|
|
609
770
|
const device = await playcanvas.createGraphicsDevice(this._canvas, {
|
|
610
771
|
// @ts-ignore - alpha needs to be documented
|
|
611
772
|
alpha: this._alpha,
|
|
612
773
|
antialias: this._antialias,
|
|
613
|
-
depth: this.
|
|
774
|
+
depth: this._depthBuffer,
|
|
614
775
|
deviceTypes: deviceTypes,
|
|
615
|
-
stencil: this.
|
|
776
|
+
stencil: this._stencilBuffer
|
|
616
777
|
});
|
|
617
|
-
|
|
778
|
+
// Assigned rather than resolved to a number here: the engine caps against the live
|
|
779
|
+
// window.devicePixelRatio on every resize, so an uncapped Infinity keeps following the
|
|
780
|
+
// display when a window moves between monitors of differing density.
|
|
781
|
+
device.maxPixelRatio = this._maxPixelRatio;
|
|
618
782
|
const createOptions = new playcanvas.AppOptions();
|
|
619
783
|
createOptions.graphicsDevice = device;
|
|
620
784
|
createOptions.keyboard = new playcanvas.Keyboard(window);
|
|
@@ -679,10 +843,11 @@
|
|
|
679
843
|
createOptions.lightmapper = playcanvas.Lightmapper;
|
|
680
844
|
createOptions.batchManager = playcanvas.BatchManager;
|
|
681
845
|
createOptions.xr = playcanvas.XrManager;
|
|
682
|
-
|
|
683
|
-
this.app
|
|
684
|
-
|
|
685
|
-
|
|
846
|
+
const app = new playcanvas.AppBase(this._canvas);
|
|
847
|
+
this._app = app;
|
|
848
|
+
app.init(createOptions);
|
|
849
|
+
app.setCanvasFillMode(playcanvas.FILLMODE_FILL_WINDOW);
|
|
850
|
+
app.setCanvasResolution(playcanvas.RESOLUTION_AUTO);
|
|
686
851
|
this._pickerCreate();
|
|
687
852
|
// Get all pc-asset elements that are direct children of the pc-app element
|
|
688
853
|
const assetElements = this.querySelectorAll(':scope > pc-asset');
|
|
@@ -690,7 +855,7 @@
|
|
|
690
855
|
assetElement.createAsset();
|
|
691
856
|
const asset = assetElement.asset;
|
|
692
857
|
if (asset) {
|
|
693
|
-
|
|
858
|
+
app.assets.add(asset);
|
|
694
859
|
}
|
|
695
860
|
});
|
|
696
861
|
// Get all pc-material elements that are direct children of the pc-app element
|
|
@@ -701,29 +866,57 @@
|
|
|
701
866
|
// Create all entities
|
|
702
867
|
const entityElements = this.querySelectorAll('pc-entity');
|
|
703
868
|
Array.from(entityElements).forEach((entityElement) => {
|
|
704
|
-
entityElement.createEntity(
|
|
869
|
+
entityElement.createEntity(app);
|
|
705
870
|
});
|
|
706
871
|
// Build hierarchy
|
|
707
872
|
entityElements.forEach((entityElement) => {
|
|
708
|
-
entityElement.buildHierarchy(
|
|
873
|
+
entityElement.buildHierarchy(app);
|
|
709
874
|
});
|
|
710
875
|
this._hierarchyReady = true;
|
|
876
|
+
// Forward the engine's preload lifecycle as DOM ProgressEvents on this element. The
|
|
877
|
+
// listener must be attached before preload() is called: an asset that is already loaded
|
|
878
|
+
// ticks synchronously inside it.
|
|
879
|
+
const total = app.assets.list({ preload: true }).length;
|
|
880
|
+
let loaded = 0;
|
|
881
|
+
const onPreloadProgress = () => {
|
|
882
|
+
loaded += 1;
|
|
883
|
+
this._loadProgress = loaded / total;
|
|
884
|
+
this._bar?.progress(loaded, total);
|
|
885
|
+
this.dispatchEvent(new ProgressEvent('progress', { lengthComputable: true, loaded, total }));
|
|
886
|
+
};
|
|
887
|
+
app.on('preload:progress', onPreloadProgress);
|
|
888
|
+
this._loadProgress = total === 0 ? 1 : 0;
|
|
889
|
+
this._bar?.progress(0, total);
|
|
890
|
+
this.dispatchEvent(new ProgressEvent('progress', { lengthComputable: true, loaded: 0, total }));
|
|
711
891
|
// Load assets before starting the application
|
|
712
|
-
|
|
892
|
+
app.preload(() => {
|
|
893
|
+
// Scope the counter to this preload pass, so a later app.preload() call by user code
|
|
894
|
+
// cannot push `loaded` past `total`
|
|
895
|
+
app.off('preload:progress', onPreloadProgress);
|
|
896
|
+
this._loadProgress = 1;
|
|
713
897
|
// Start the application
|
|
714
|
-
|
|
898
|
+
app.start();
|
|
899
|
+
// Dismiss the bar only once a frame has actually rendered; ready fires before the
|
|
900
|
+
// first rAF tick
|
|
901
|
+
app.once('frameend', () => this._bar?.complete());
|
|
715
902
|
// Handle window resize to keep the canvas responsive
|
|
716
903
|
window.addEventListener('resize', this._onWindowResize);
|
|
717
904
|
this._onReady();
|
|
718
905
|
});
|
|
719
906
|
}
|
|
720
907
|
disconnectedCallback() {
|
|
908
|
+
this._optionsLocked = false;
|
|
721
909
|
this._pickerDestroy();
|
|
722
|
-
// Clean up the application
|
|
723
|
-
|
|
724
|
-
|
|
910
|
+
// Clean up the application. Destroying it destroys every entity, whose destroy hooks
|
|
911
|
+
// unregister them - clear() covers any entity the engine no longer reached.
|
|
912
|
+
if (this._app) {
|
|
913
|
+
this._app.destroy();
|
|
725
914
|
this._app = null;
|
|
726
915
|
}
|
|
916
|
+
this._entityElements.clear();
|
|
917
|
+
this._loadProgress = 0;
|
|
918
|
+
this._bar?.destroy();
|
|
919
|
+
this._bar = null;
|
|
727
920
|
// Remove event listeners
|
|
728
921
|
window.removeEventListener('resize', this._onWindowResize);
|
|
729
922
|
// Remove the canvas
|
|
@@ -740,10 +933,17 @@
|
|
|
740
933
|
_pickerCreate() {
|
|
741
934
|
const { width, height } = this.app.graphicsDevice;
|
|
742
935
|
this._picker = new playcanvas.Picker(this.app, width, height);
|
|
743
|
-
// Create bound handlers but don't attach them yet
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
936
|
+
// Create bound handlers but don't attach them yet. The handlers pick asynchronously, so
|
|
937
|
+
// each is wrapped to discard the promise - a listener must not return one, and nothing
|
|
938
|
+
// awaits the result.
|
|
939
|
+
const listener = (handler) => {
|
|
940
|
+
return (event) => {
|
|
941
|
+
handler.call(this, event);
|
|
942
|
+
};
|
|
943
|
+
};
|
|
944
|
+
this._pointerHandlers.pointermove = listener(this._onPointerMove);
|
|
945
|
+
this._pointerHandlers.pointerdown = listener(this._onPointerDown);
|
|
946
|
+
this._pointerHandlers.pointerup = listener(this._onPointerUp);
|
|
747
947
|
// Listen for pointer listeners being added/removed
|
|
748
948
|
['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'].forEach((type) => {
|
|
749
949
|
this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type));
|
|
@@ -779,6 +979,74 @@
|
|
|
779
979
|
pointermove: false
|
|
780
980
|
};
|
|
781
981
|
}
|
|
982
|
+
/**
|
|
983
|
+
* Registers the element that created an entity. Called by EntityElement when it creates its
|
|
984
|
+
* entity.
|
|
985
|
+
*
|
|
986
|
+
* @param entity - The entity.
|
|
987
|
+
* @param element - The element that created it.
|
|
988
|
+
* @ignore
|
|
989
|
+
*/
|
|
990
|
+
_registerEntityElement(entity, element) {
|
|
991
|
+
this._entityElements.set(entity, element);
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Removes the registration for a destroyed entity. Called by EntityElement.
|
|
995
|
+
*
|
|
996
|
+
* @param entity - The entity.
|
|
997
|
+
* @ignore
|
|
998
|
+
*/
|
|
999
|
+
_unregisterEntityElement(entity) {
|
|
1000
|
+
this._entityElements.delete(entity);
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Returns the `<pc-entity>` element whose backing entity is `entity`, or `null` if the
|
|
1004
|
+
* entity was not created by an element of this application - for example, a node inside a
|
|
1005
|
+
* model's instantiated hierarchy, or an entity created through the engine API.
|
|
1006
|
+
*
|
|
1007
|
+
* @param entity - The entity to look up.
|
|
1008
|
+
* @returns The element backing the entity, or `null`.
|
|
1009
|
+
*/
|
|
1010
|
+
elementFromEntity(entity) {
|
|
1011
|
+
return this._entityElements.get(entity) ?? null;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Resolves the element that owns a picked node: the nearest node up the parent chain -
|
|
1015
|
+
* starting with the node itself - that was created by a `<pc-entity>` of this application.
|
|
1016
|
+
* A hit inside a model's instantiated hierarchy therefore resolves to the element hosting
|
|
1017
|
+
* the model.
|
|
1018
|
+
*
|
|
1019
|
+
* @param node - The picked node, or `null`.
|
|
1020
|
+
* @returns The owning element, or `null`.
|
|
1021
|
+
*/
|
|
1022
|
+
_elementFromNode(node) {
|
|
1023
|
+
while (node !== null) {
|
|
1024
|
+
const element = this._entityElements.get(node);
|
|
1025
|
+
if (element) {
|
|
1026
|
+
return element;
|
|
1027
|
+
}
|
|
1028
|
+
node = node.parent;
|
|
1029
|
+
}
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Like {@link _elementFromNode}, but skips elements without a listener for `type`, so a hit
|
|
1034
|
+
* on an unlistened child still reaches a listening ancestor.
|
|
1035
|
+
*
|
|
1036
|
+
* @param node - The picked node, or `null`.
|
|
1037
|
+
* @param type - The pointer event type a listener is required for.
|
|
1038
|
+
* @returns The nearest listening element, or `null`.
|
|
1039
|
+
*/
|
|
1040
|
+
_elementWithListener(node, type) {
|
|
1041
|
+
while (node !== null) {
|
|
1042
|
+
const element = this._entityElements.get(node);
|
|
1043
|
+
if (element?.hasListeners(type)) {
|
|
1044
|
+
return element;
|
|
1045
|
+
}
|
|
1046
|
+
node = node.parent;
|
|
1047
|
+
}
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
782
1050
|
// New helper to convert CSS coordinates to canvas (picker) coordinates
|
|
783
1051
|
_getPickerCoordinates(event) {
|
|
784
1052
|
// Get the canvas' bounding rectangle in CSS pixels.
|
|
@@ -791,30 +1059,42 @@
|
|
|
791
1059
|
const y = (event.clientY - canvasRect.top) * scaleY;
|
|
792
1060
|
return { x, y };
|
|
793
1061
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1062
|
+
/**
|
|
1063
|
+
* Picks the scene under the pointer and returns the graph node that was hit, or `null`.
|
|
1064
|
+
*
|
|
1065
|
+
* The read back is asynchronous because the synchronous {@link Picker.getSelection} is not
|
|
1066
|
+
* supported on WebGPU, where it returns an empty selection rather than failing - which
|
|
1067
|
+
* silently disabled every `onpointer*` handler once WebGPU became the resolved backend. The
|
|
1068
|
+
* async variant works on both backends and does not block the main thread on a GPU read.
|
|
1069
|
+
*
|
|
1070
|
+
* @param event - The pointer event to pick under.
|
|
1071
|
+
* @returns The graph node under the pointer, or `null` if nothing was hit.
|
|
1072
|
+
*/
|
|
1073
|
+
async _pickNode(event) {
|
|
797
1074
|
const camera = this.app.root.findComponent('camera');
|
|
798
1075
|
if (!camera)
|
|
799
|
-
return;
|
|
800
|
-
// Use the helper to convert event coordinates into canvas/picker coordinates.
|
|
1076
|
+
return null;
|
|
801
1077
|
const { x, y } = this._getPickerCoordinates(event);
|
|
802
1078
|
this._picker.prepare(camera, this.app.scene);
|
|
803
|
-
const selection = this._picker.
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1079
|
+
const selection = await this._picker.getSelectionAsync(x, y);
|
|
1080
|
+
if (selection.length === 0)
|
|
1081
|
+
return null;
|
|
1082
|
+
const item = selection[0];
|
|
1083
|
+
return item instanceof playcanvas.MeshInstance ? item.node : item.entity;
|
|
1084
|
+
}
|
|
1085
|
+
async _onPointerMove(event) {
|
|
1086
|
+
if (!this._picker || !this.app)
|
|
1087
|
+
return;
|
|
1088
|
+
// Moves arrive faster than a pick resolves, so results can land out of order. Only the
|
|
1089
|
+
// newest pick may update the hover state - an older one describes a pointer position the
|
|
1090
|
+
// user has already left.
|
|
1091
|
+
const token = ++this._pickToken;
|
|
1092
|
+
const node = await this._pickNode(event);
|
|
1093
|
+
if (token !== this._pickToken || !this._picker)
|
|
1094
|
+
return;
|
|
1095
|
+
// The hovered element is the nearest one up the node's parent chain, listening or not -
|
|
1096
|
+
// dispatch is gated per event type below
|
|
1097
|
+
const newHoverEntity = this._elementFromNode(node);
|
|
818
1098
|
// Handle enter/leave events
|
|
819
1099
|
if (this._hoveredEntity !== newHoverEntity) {
|
|
820
1100
|
if (this._hoveredEntity && this._hoveredEntity.hasListeners('pointerleave')) {
|
|
@@ -831,46 +1111,26 @@
|
|
|
831
1111
|
newHoverEntity.dispatchEvent(new PointerEvent('pointermove', event));
|
|
832
1112
|
}
|
|
833
1113
|
}
|
|
834
|
-
_onPointerDown(event) {
|
|
1114
|
+
async _onPointerDown(event) {
|
|
835
1115
|
if (!this._picker || !this.app)
|
|
836
1116
|
return;
|
|
837
|
-
const
|
|
838
|
-
if (!
|
|
839
|
-
return;
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
const selection = this._picker.getSelection(x, y);
|
|
844
|
-
if (selection.length > 0) {
|
|
845
|
-
const item = selection[0];
|
|
846
|
-
let currentNode = item instanceof playcanvas.MeshInstance ? item.node : item.entity;
|
|
847
|
-
while (currentNode !== null) {
|
|
848
|
-
const entityElement = this.querySelector(`pc-entity[name="${currentNode.name}"]`);
|
|
849
|
-
if (entityElement && entityElement.hasListeners('pointerdown')) {
|
|
850
|
-
entityElement.dispatchEvent(new PointerEvent('pointerdown', event));
|
|
851
|
-
break;
|
|
852
|
-
}
|
|
853
|
-
currentNode = currentNode.parent;
|
|
854
|
-
}
|
|
1117
|
+
const node = await this._pickNode(event);
|
|
1118
|
+
if (!this._picker)
|
|
1119
|
+
return; // the element disconnected while the pick was in flight
|
|
1120
|
+
const entityElement = this._elementWithListener(node, 'pointerdown');
|
|
1121
|
+
if (entityElement) {
|
|
1122
|
+
entityElement.dispatchEvent(new PointerEvent('pointerdown', event));
|
|
855
1123
|
}
|
|
856
1124
|
}
|
|
857
|
-
_onPointerUp(event) {
|
|
1125
|
+
async _onPointerUp(event) {
|
|
858
1126
|
if (!this._picker || !this.app)
|
|
859
1127
|
return;
|
|
860
|
-
const
|
|
861
|
-
if (!
|
|
862
|
-
return;
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const selection = this._picker.getSelection(x, y);
|
|
867
|
-
if (selection.length > 0) {
|
|
868
|
-
const item = selection[0];
|
|
869
|
-
const node = item instanceof playcanvas.MeshInstance ? item.node : item.entity;
|
|
870
|
-
const entityElement = this.querySelector(`pc-entity[name="${node.name}"]`);
|
|
871
|
-
if (entityElement && entityElement.hasListeners('pointerup')) {
|
|
872
|
-
entityElement.dispatchEvent(new PointerEvent('pointerup', event));
|
|
873
|
-
}
|
|
1128
|
+
const node = await this._pickNode(event);
|
|
1129
|
+
if (!this._picker)
|
|
1130
|
+
return; // the element disconnected while the pick was in flight
|
|
1131
|
+
const entityElement = this._elementWithListener(node, 'pointerup');
|
|
1132
|
+
if (entityElement) {
|
|
1133
|
+
entityElement.dispatchEvent(new PointerEvent('pointerup', event));
|
|
874
1134
|
}
|
|
875
1135
|
}
|
|
876
1136
|
_onPointerListenerAdded(type) {
|
|
@@ -899,28 +1159,43 @@
|
|
|
899
1159
|
}
|
|
900
1160
|
}
|
|
901
1161
|
/**
|
|
902
|
-
*
|
|
1162
|
+
* Warns that a graphics option was written too late to have any effect. These options are read
|
|
1163
|
+
* once, when the element connects and creates its graphics device, so a later write updates
|
|
1164
|
+
* only the element's own property - silently, without this.
|
|
1165
|
+
*
|
|
1166
|
+
* @param name - The name of the option, as its attribute.
|
|
1167
|
+
*/
|
|
1168
|
+
_warnIfBooted(name) {
|
|
1169
|
+
if (this._optionsLocked) {
|
|
1170
|
+
console.warn(`Attribute '${name}' on <pc-app> is only read when the application boots, so this change has no effect. Set it before the element is connected, or remove and re-insert the element to reboot with the new value.`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Sets whether the frame buffer has an alpha channel, which is what lets the page show through
|
|
1175
|
+
* wherever the scene has not drawn. Read only when the application boots.
|
|
903
1176
|
* @param value - The alpha flag.
|
|
904
1177
|
*/
|
|
905
1178
|
set alpha(value) {
|
|
1179
|
+
this._warnIfBooted('alpha');
|
|
906
1180
|
this._alpha = value;
|
|
907
1181
|
}
|
|
908
1182
|
/**
|
|
909
|
-
* Gets the alpha
|
|
1183
|
+
* Gets whether the frame buffer has an alpha channel.
|
|
910
1184
|
* @returns The alpha flag.
|
|
911
1185
|
*/
|
|
912
1186
|
get alpha() {
|
|
913
1187
|
return this._alpha;
|
|
914
1188
|
}
|
|
915
1189
|
/**
|
|
916
|
-
* Sets the
|
|
1190
|
+
* Sets whether the frame buffer is anti-aliased. Read only when the application boots.
|
|
917
1191
|
* @param value - The antialias flag.
|
|
918
1192
|
*/
|
|
919
1193
|
set antialias(value) {
|
|
1194
|
+
this._warnIfBooted('antialias');
|
|
920
1195
|
this._antialias = value;
|
|
921
1196
|
}
|
|
922
1197
|
/**
|
|
923
|
-
* Gets the
|
|
1198
|
+
* Gets whether the frame buffer is anti-aliased.
|
|
924
1199
|
* @returns The antialias flag.
|
|
925
1200
|
*/
|
|
926
1201
|
get antialias() {
|
|
@@ -928,10 +1203,11 @@
|
|
|
928
1203
|
}
|
|
929
1204
|
/**
|
|
930
1205
|
* Sets the graphics backend. Defaults to 'webgpu', which falls back to 'webgl2' if WebGPU
|
|
931
|
-
* is not supported by the browser.
|
|
1206
|
+
* is not supported by the browser. Read only when the application boots.
|
|
932
1207
|
* @param value - The graphics backend ('webgpu', 'webgl2', or 'null').
|
|
933
1208
|
*/
|
|
934
1209
|
set backend(value) {
|
|
1210
|
+
this._warnIfBooted('backend');
|
|
935
1211
|
this._backend = value;
|
|
936
1212
|
}
|
|
937
1213
|
/**
|
|
@@ -942,18 +1218,20 @@
|
|
|
942
1218
|
return this._backend;
|
|
943
1219
|
}
|
|
944
1220
|
/**
|
|
945
|
-
* Sets the depth
|
|
946
|
-
*
|
|
1221
|
+
* Sets whether the frame buffer has a depth buffer, which the renderer needs to resolve which
|
|
1222
|
+
* surface is nearest the camera. Read only when the application boots.
|
|
1223
|
+
* @param value - The depth buffer flag.
|
|
947
1224
|
*/
|
|
948
|
-
set
|
|
949
|
-
this.
|
|
1225
|
+
set depthBuffer(value) {
|
|
1226
|
+
this._warnIfBooted('depth-buffer');
|
|
1227
|
+
this._depthBuffer = value;
|
|
950
1228
|
}
|
|
951
1229
|
/**
|
|
952
|
-
* Gets the depth
|
|
953
|
-
* @returns The depth flag.
|
|
1230
|
+
* Gets whether the frame buffer has a depth buffer.
|
|
1231
|
+
* @returns The depth buffer flag.
|
|
954
1232
|
*/
|
|
955
|
-
get
|
|
956
|
-
return this.
|
|
1233
|
+
get depthBuffer() {
|
|
1234
|
+
return this._depthBuffer;
|
|
957
1235
|
}
|
|
958
1236
|
/**
|
|
959
1237
|
* Gets the hierarchy ready flag.
|
|
@@ -964,39 +1242,68 @@
|
|
|
964
1242
|
return this._hierarchyReady;
|
|
965
1243
|
}
|
|
966
1244
|
/**
|
|
967
|
-
* Sets the
|
|
968
|
-
*
|
|
969
|
-
*
|
|
1245
|
+
* Sets whether the application shows its built-in loading bar while it boots and preloads its
|
|
1246
|
+
* assets. Enabled by default; setting `false` removes the bar immediately, while setting
|
|
1247
|
+
* `true` has no effect until the element is next connected. The bar can be themed with the
|
|
1248
|
+
* CSS custom properties `--pc-loading-bar-color`, `--pc-loading-bar-background` and
|
|
1249
|
+
* `--pc-loading-bar-height`.
|
|
1250
|
+
* @param value - The loading bar flag.
|
|
970
1251
|
*/
|
|
971
|
-
set
|
|
972
|
-
this.
|
|
1252
|
+
set loadingBar(value) {
|
|
1253
|
+
this._loadingBar = value;
|
|
1254
|
+
if (!value && this._bar) {
|
|
1255
|
+
this._bar.destroy();
|
|
1256
|
+
this._bar = null;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Gets whether the application shows its built-in loading bar while it boots and preloads
|
|
1261
|
+
* its assets.
|
|
1262
|
+
* @returns The loading bar flag.
|
|
1263
|
+
*/
|
|
1264
|
+
get loadingBar() {
|
|
1265
|
+
return this._loadingBar;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Sets the cap on the pixel ratio the application renders at. The canvas is sized by the
|
|
1269
|
+
* smaller of this value and the display's own device pixel ratio, so the default of `Infinity`
|
|
1270
|
+
* renders at full physical resolution, `1` renders at CSS resolution, and an intermediate
|
|
1271
|
+
* value such as `2` keeps a dense display sharp without paying for every one of its pixels.
|
|
1272
|
+
* Must be greater than 0. Unlike the other graphics options, this applies immediately.
|
|
1273
|
+
* @param value - The maximum pixel ratio.
|
|
1274
|
+
*/
|
|
1275
|
+
set maxPixelRatio(value) {
|
|
1276
|
+
this._maxPixelRatio = value;
|
|
973
1277
|
if (this.app) {
|
|
974
|
-
this.app.graphicsDevice.maxPixelRatio = value
|
|
1278
|
+
this.app.graphicsDevice.maxPixelRatio = value;
|
|
1279
|
+
this.app.resizeCanvas();
|
|
975
1280
|
}
|
|
976
1281
|
}
|
|
977
1282
|
/**
|
|
978
|
-
* Gets the
|
|
979
|
-
* @returns The
|
|
1283
|
+
* Gets the cap on the pixel ratio the application renders at.
|
|
1284
|
+
* @returns The maximum pixel ratio.
|
|
980
1285
|
*/
|
|
981
|
-
get
|
|
982
|
-
return this.
|
|
1286
|
+
get maxPixelRatio() {
|
|
1287
|
+
return this._maxPixelRatio;
|
|
983
1288
|
}
|
|
984
1289
|
/**
|
|
985
|
-
* Sets the stencil
|
|
986
|
-
*
|
|
1290
|
+
* Sets whether the frame buffer has a stencil buffer, which stencil-based effects and UI
|
|
1291
|
+
* masking need. Read only when the application boots.
|
|
1292
|
+
* @param value - The stencil buffer flag.
|
|
987
1293
|
*/
|
|
988
|
-
set
|
|
989
|
-
this.
|
|
1294
|
+
set stencilBuffer(value) {
|
|
1295
|
+
this._warnIfBooted('stencil-buffer');
|
|
1296
|
+
this._stencilBuffer = value;
|
|
990
1297
|
}
|
|
991
1298
|
/**
|
|
992
|
-
* Gets the stencil
|
|
993
|
-
* @returns The stencil flag.
|
|
1299
|
+
* Gets whether the frame buffer has a stencil buffer.
|
|
1300
|
+
* @returns The stencil buffer flag.
|
|
994
1301
|
*/
|
|
995
|
-
get
|
|
996
|
-
return this.
|
|
1302
|
+
get stencilBuffer() {
|
|
1303
|
+
return this._stencilBuffer;
|
|
997
1304
|
}
|
|
998
1305
|
static get observedAttributes() {
|
|
999
|
-
return ['alpha', 'antialias', 'backend', 'depth', '
|
|
1306
|
+
return ['alpha', 'antialias', 'backend', 'depth-buffer', 'loading-bar', 'max-pixel-ratio', 'stencil-buffer'];
|
|
1000
1307
|
}
|
|
1001
1308
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
1002
1309
|
switch (name) {
|
|
@@ -1009,14 +1316,17 @@
|
|
|
1009
1316
|
case 'backend':
|
|
1010
1317
|
this.backend = parseEnum(newValue, ['webgpu', 'webgl2', 'null'], 'webgpu', name);
|
|
1011
1318
|
break;
|
|
1012
|
-
case 'depth':
|
|
1013
|
-
this.
|
|
1319
|
+
case 'depth-buffer':
|
|
1320
|
+
this.depthBuffer = parseBool(newValue, true);
|
|
1014
1321
|
break;
|
|
1015
|
-
case '
|
|
1016
|
-
this.
|
|
1322
|
+
case 'loading-bar':
|
|
1323
|
+
this.loadingBar = parseBool(newValue, true);
|
|
1017
1324
|
break;
|
|
1018
|
-
case '
|
|
1019
|
-
this.
|
|
1325
|
+
case 'max-pixel-ratio':
|
|
1326
|
+
this.maxPixelRatio = parseNumber(newValue, Infinity, name);
|
|
1327
|
+
break;
|
|
1328
|
+
case 'stencil-buffer':
|
|
1329
|
+
this.stencilBuffer = parseBool(newValue, true);
|
|
1020
1330
|
break;
|
|
1021
1331
|
}
|
|
1022
1332
|
}
|
|
@@ -1086,9 +1396,15 @@
|
|
|
1086
1396
|
_built = false;
|
|
1087
1397
|
_entity = null;
|
|
1088
1398
|
/**
|
|
1089
|
-
* The
|
|
1090
|
-
*
|
|
1091
|
-
|
|
1399
|
+
* The application element this entity is registered with, cached at creation time so the
|
|
1400
|
+
* entity can be unregistered even once this element has left the DOM.
|
|
1401
|
+
*/
|
|
1402
|
+
_appElement = null;
|
|
1403
|
+
/**
|
|
1404
|
+
* The PlayCanvas entity instance. `null` until the element is ready, and again once it has
|
|
1405
|
+
* been removed from the document — await {@link whenReady} or the element's `ready()`
|
|
1406
|
+
* promise before accessing it.
|
|
1407
|
+
* @returns The entity instance, or `null`.
|
|
1092
1408
|
*/
|
|
1093
1409
|
get entity() {
|
|
1094
1410
|
return this._entity;
|
|
@@ -1100,17 +1416,40 @@
|
|
|
1100
1416
|
if (this._entity) {
|
|
1101
1417
|
return;
|
|
1102
1418
|
}
|
|
1103
|
-
//
|
|
1104
|
-
|
|
1419
|
+
// Seed from the cached fields rather than re-reading the attributes. Every observed
|
|
1420
|
+
// attribute is routed through its property setter by attributeChangedCallback, so the field
|
|
1421
|
+
// already holds the parsed attribute value - and it also holds anything assigned through the
|
|
1422
|
+
// property API before the app booted, which reading the attribute back would discard.
|
|
1423
|
+
const entity = new playcanvas.Entity(this._name, app);
|
|
1105
1424
|
this._entity = entity;
|
|
1106
|
-
entity.enabled =
|
|
1107
|
-
entity.setLocalPosition(
|
|
1108
|
-
entity.setLocalEulerAngles(
|
|
1109
|
-
entity.setLocalScale(
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1425
|
+
entity.enabled = this._enabled;
|
|
1426
|
+
entity.setLocalPosition(this._position);
|
|
1427
|
+
entity.setLocalEulerAngles(this._rotation);
|
|
1428
|
+
entity.setLocalScale(this._scale);
|
|
1429
|
+
if (this._tags.length > 0) {
|
|
1430
|
+
entity.tags.add(this._tags);
|
|
1431
|
+
}
|
|
1432
|
+
// Register with the owning application, which joins engine nodes back to elements by
|
|
1433
|
+
// identity (never by name), and hook the entity's destruction. The engine fires 'destroy'
|
|
1434
|
+
// for every entity in a destroyed subtree, so the element learns of its entity's death no
|
|
1435
|
+
// matter who causes it: this element, an ancestor, the whole application, or a user
|
|
1436
|
+
// script calling entity.destroy().
|
|
1437
|
+
this._appElement = this.closestApp;
|
|
1438
|
+
this._appElement?._registerEntityElement(entity, this);
|
|
1439
|
+
entity.once('destroy', this._onEntityDestroy, this);
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Handles the destruction of the backing entity. Resets the element so a later re-insertion
|
|
1443
|
+
* starts clean: `_built` must be cleared alongside `_entity`, or buildHierarchy would bail
|
|
1444
|
+
* and a re-created entity would never be parented.
|
|
1445
|
+
*
|
|
1446
|
+
* @param entity - The entity that was destroyed.
|
|
1447
|
+
*/
|
|
1448
|
+
_onEntityDestroy(entity) {
|
|
1449
|
+
this._appElement?._unregisterEntityElement(entity);
|
|
1450
|
+
this._appElement = null;
|
|
1451
|
+
this._entity = null;
|
|
1452
|
+
this._built = false;
|
|
1114
1453
|
}
|
|
1115
1454
|
buildHierarchy(app) {
|
|
1116
1455
|
if (!this.entity || this._built)
|
|
@@ -1128,8 +1467,15 @@
|
|
|
1128
1467
|
connectedCallback() {
|
|
1129
1468
|
// Wait for app to be ready
|
|
1130
1469
|
const closestApp = this.closestApp;
|
|
1131
|
-
if (!closestApp)
|
|
1470
|
+
if (!closestApp) {
|
|
1471
|
+
// An entity outside an application is inert and never becomes ready, so awaiting it
|
|
1472
|
+
// hangs. Warn rather than fail silently, naming the parent it requires, as every other
|
|
1473
|
+
// misplaced element does.
|
|
1474
|
+
const name = this.getAttribute('name');
|
|
1475
|
+
const label = name ? ` '${name}'` : '';
|
|
1476
|
+
console.warn(`pc-entity${label} must be a descendant of pc-app - entity not created`);
|
|
1132
1477
|
return;
|
|
1478
|
+
}
|
|
1133
1479
|
// If app is already running, create entity immediately
|
|
1134
1480
|
if (closestApp.hierarchyReady) {
|
|
1135
1481
|
const app = closestApp.app;
|
|
@@ -1146,17 +1492,11 @@
|
|
|
1146
1492
|
}
|
|
1147
1493
|
}
|
|
1148
1494
|
disconnectedCallback() {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
});
|
|
1155
|
-
// Destroy the entity
|
|
1156
|
-
this.entity.destroy();
|
|
1157
|
-
this._entity = null;
|
|
1158
|
-
this._built = false;
|
|
1159
|
-
}
|
|
1495
|
+
// Destroying the entity destroys its whole subtree, and the engine fires 'destroy' for
|
|
1496
|
+
// every entity in it - so _onEntityDestroy resets this element AND every descendant
|
|
1497
|
+
// element before the descendants' own disconnectedCallbacks run. Their entities are null
|
|
1498
|
+
// by then, making this call a no-op for them.
|
|
1499
|
+
this._entity?.destroy();
|
|
1160
1500
|
}
|
|
1161
1501
|
/**
|
|
1162
1502
|
* Sets the enabled state of the entity.
|
|
@@ -1605,6 +1945,13 @@
|
|
|
1605
1945
|
* @attribute {number} pixels-per-unit - For a `sprite` asset, the number of pixels per world unit.
|
|
1606
1946
|
* @attribute {'simple' | 'sliced' | 'tiled'} render-mode - For a `sprite` asset, how the sprite is
|
|
1607
1947
|
* rendered when resized.
|
|
1948
|
+
*
|
|
1949
|
+
* @fires {Event} load - Fired each time the asset finishes loading, including a `lazy` asset
|
|
1950
|
+
* loaded later and any subsequent reloads. Does not bubble — listen on this element, or use a
|
|
1951
|
+
* capture-phase listener on an ancestor to observe every asset.
|
|
1952
|
+
* @fires {ErrorEvent} error - Fired when the asset fails to load, with the engine's error in
|
|
1953
|
+
* `message`. Does not bubble. The element still becomes ready — readiness means the load settled,
|
|
1954
|
+
* not that it succeeded.
|
|
1608
1955
|
*/
|
|
1609
1956
|
class AssetElement extends AsyncElement {
|
|
1610
1957
|
_lazy = false;
|
|
@@ -1648,6 +1995,14 @@
|
|
|
1648
1995
|
disconnectedCallback() {
|
|
1649
1996
|
this.destroyAsset();
|
|
1650
1997
|
}
|
|
1998
|
+
_onAssetLoad() {
|
|
1999
|
+
this.dispatchEvent(new Event('load'));
|
|
2000
|
+
}
|
|
2001
|
+
_onAssetError(err) {
|
|
2002
|
+
this.dispatchEvent(new ErrorEvent('error', {
|
|
2003
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2004
|
+
}));
|
|
2005
|
+
}
|
|
1651
2006
|
createAsset() {
|
|
1652
2007
|
const id = this.getAttribute('id') || '';
|
|
1653
2008
|
const src = this.getAttribute('src') || '';
|
|
@@ -1682,6 +2037,10 @@
|
|
|
1682
2037
|
this.asset = new playcanvas.Asset(id, type, src ? { url: src } : null, data);
|
|
1683
2038
|
}
|
|
1684
2039
|
this.asset.preload = !this._lazy;
|
|
2040
|
+
// Forward the engine asset's load outcome as DOM events on this element, like <img>.
|
|
2041
|
+
// Attached before the asset joins the registry, which is what starts a preloaded load.
|
|
2042
|
+
this.asset.on('load', this._onAssetLoad, this);
|
|
2043
|
+
this.asset.on('error', this._onAssetError, this);
|
|
1685
2044
|
}
|
|
1686
2045
|
/**
|
|
1687
2046
|
* Builds the `data` object for the asset from an optional inline `data` attribute (JSON) and,
|
|
@@ -1736,6 +2095,9 @@
|
|
|
1736
2095
|
}
|
|
1737
2096
|
destroyAsset() {
|
|
1738
2097
|
if (this.asset) {
|
|
2098
|
+
// A caller that keeps the Asset alive must not dispatch on a removed element
|
|
2099
|
+
this.asset.off('load', this._onAssetLoad, this);
|
|
2100
|
+
this.asset.off('error', this._onAssetError, this);
|
|
1739
2101
|
// Deregister first so unload() can still notify the registry
|
|
1740
2102
|
this.asset.registry?.remove(this.asset);
|
|
1741
2103
|
this.asset.unload();
|
|
@@ -1831,9 +2193,10 @@
|
|
|
1831
2193
|
this._appElement = null;
|
|
1832
2194
|
}
|
|
1833
2195
|
/**
|
|
1834
|
-
* The PlayCanvas component instance.
|
|
1835
|
-
* {@link whenReady} or the
|
|
1836
|
-
*
|
|
2196
|
+
* The PlayCanvas component instance. `null` until the element is ready, and also for an
|
|
2197
|
+
* element that is not a descendant of a `<pc-entity>` — await {@link whenReady} or the
|
|
2198
|
+
* element's `ready()` promise before accessing it.
|
|
2199
|
+
* @returns The component instance, or `null`.
|
|
1837
2200
|
*/
|
|
1838
2201
|
get component() {
|
|
1839
2202
|
return this._component;
|
|
@@ -2280,6 +2643,10 @@
|
|
|
2280
2643
|
}
|
|
2281
2644
|
customElements.define('pc-button', ButtonComponentElement);
|
|
2282
2645
|
|
|
2646
|
+
const projections = new Map([
|
|
2647
|
+
['perspective', playcanvas.PROJECTION_PERSPECTIVE],
|
|
2648
|
+
['orthographic', playcanvas.PROJECTION_ORTHOGRAPHIC]
|
|
2649
|
+
]);
|
|
2283
2650
|
const tonemaps = new Map([
|
|
2284
2651
|
['none', playcanvas.TONEMAP_NONE],
|
|
2285
2652
|
['linear', playcanvas.TONEMAP_LINEAR],
|
|
@@ -2310,7 +2677,7 @@
|
|
|
2310
2677
|
_gamma = 'srgb';
|
|
2311
2678
|
_horizontalFov = false;
|
|
2312
2679
|
_nearClip = 0.1;
|
|
2313
|
-
|
|
2680
|
+
_projection = 'perspective';
|
|
2314
2681
|
_orthoHeight = 10;
|
|
2315
2682
|
_priority = 0;
|
|
2316
2683
|
_rect = new playcanvas.Vec4(0, 0, 1, 1);
|
|
@@ -2334,12 +2701,12 @@
|
|
|
2334
2701
|
gammaCorrection: this._gamma === 'srgb' ? playcanvas.GAMMA_SRGB : playcanvas.GAMMA_NONE,
|
|
2335
2702
|
horizontalFov: this._horizontalFov,
|
|
2336
2703
|
nearClip: this._nearClip,
|
|
2337
|
-
projection: this.
|
|
2704
|
+
projection: projections.get(this._projection) ?? playcanvas.PROJECTION_PERSPECTIVE,
|
|
2338
2705
|
orthoHeight: this._orthoHeight,
|
|
2339
2706
|
priority: this._priority,
|
|
2340
2707
|
rect: this._rect,
|
|
2341
2708
|
scissorRect: this._scissorRect,
|
|
2342
|
-
toneMapping: tonemaps.get(this._tonemap)
|
|
2709
|
+
toneMapping: tonemaps.get(this._tonemap) ?? playcanvas.TONEMAP_NONE
|
|
2343
2710
|
};
|
|
2344
2711
|
}
|
|
2345
2712
|
get xrAvailable() {
|
|
@@ -2581,23 +2948,6 @@
|
|
|
2581
2948
|
get nearClip() {
|
|
2582
2949
|
return this._nearClip;
|
|
2583
2950
|
}
|
|
2584
|
-
/**
|
|
2585
|
-
* Sets the orthographic projection of the camera.
|
|
2586
|
-
* @param value - The orthographic projection.
|
|
2587
|
-
*/
|
|
2588
|
-
set orthographic(value) {
|
|
2589
|
-
this._orthographic = value;
|
|
2590
|
-
if (this.component) {
|
|
2591
|
-
this.component.projection = value ? playcanvas.PROJECTION_ORTHOGRAPHIC : playcanvas.PROJECTION_PERSPECTIVE;
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
/**
|
|
2595
|
-
* Gets the orthographic projection of the camera.
|
|
2596
|
-
* @returns The orthographic projection.
|
|
2597
|
-
*/
|
|
2598
|
-
get orthographic() {
|
|
2599
|
-
return this._orthographic;
|
|
2600
|
-
}
|
|
2601
2951
|
/**
|
|
2602
2952
|
* Sets the orthographic height of the camera.
|
|
2603
2953
|
* @param value - The orthographic height.
|
|
@@ -2632,6 +2982,23 @@
|
|
|
2632
2982
|
get priority() {
|
|
2633
2983
|
return this._priority;
|
|
2634
2984
|
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Sets the projection of the camera. Use `orthoHeight` to size an orthographic projection.
|
|
2987
|
+
* @param value - The projection ('perspective' or 'orthographic').
|
|
2988
|
+
*/
|
|
2989
|
+
set projection(value) {
|
|
2990
|
+
this._projection = value;
|
|
2991
|
+
if (this.component) {
|
|
2992
|
+
this.component.projection = projections.get(value) ?? playcanvas.PROJECTION_PERSPECTIVE;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
/**
|
|
2996
|
+
* Gets the projection of the camera.
|
|
2997
|
+
* @returns The projection.
|
|
2998
|
+
*/
|
|
2999
|
+
get projection() {
|
|
3000
|
+
return this._projection;
|
|
3001
|
+
}
|
|
2635
3002
|
/**
|
|
2636
3003
|
* Sets the rect of the camera.
|
|
2637
3004
|
* @param value - The rect.
|
|
@@ -2698,9 +3065,9 @@
|
|
|
2698
3065
|
'gamma',
|
|
2699
3066
|
'horizontal-fov',
|
|
2700
3067
|
'near-clip',
|
|
2701
|
-
'orthographic',
|
|
2702
3068
|
'ortho-height',
|
|
2703
3069
|
'priority',
|
|
3070
|
+
'projection',
|
|
2704
3071
|
'rect',
|
|
2705
3072
|
'scissor-rect',
|
|
2706
3073
|
'tonemap'
|
|
@@ -2745,15 +3112,15 @@
|
|
|
2745
3112
|
case 'near-clip':
|
|
2746
3113
|
this.nearClip = parseNumber(newValue, 0.1, name);
|
|
2747
3114
|
break;
|
|
2748
|
-
case 'orthographic':
|
|
2749
|
-
this.orthographic = parseBool(newValue, false);
|
|
2750
|
-
break;
|
|
2751
3115
|
case 'ortho-height':
|
|
2752
3116
|
this.orthoHeight = parseNumber(newValue, 10, name);
|
|
2753
3117
|
break;
|
|
2754
3118
|
case 'priority':
|
|
2755
3119
|
this.priority = parseNumber(newValue, 0, name);
|
|
2756
3120
|
break;
|
|
3121
|
+
case 'projection':
|
|
3122
|
+
this.projection = parseEnum(newValue, projections, 'perspective', name);
|
|
3123
|
+
break;
|
|
2757
3124
|
case 'rect':
|
|
2758
3125
|
this.rect = parseVec4(newValue, new playcanvas.Vec4(0, 0, 1, 1), name);
|
|
2759
3126
|
break;
|
|
@@ -4726,8 +5093,14 @@
|
|
|
4726
5093
|
* created on insertion.
|
|
4727
5094
|
*
|
|
4728
5095
|
* The element is metal/rough by default: unlike a bare `StandardMaterial` it enables the metalness
|
|
4729
|
-
* workflow, which is what the `metalness-*` attributes assume and what glTF means by PBR.
|
|
4730
|
-
* `
|
|
5096
|
+
* workflow, which is what the `metalness-*` attributes assume and what glTF means by PBR. It also
|
|
5097
|
+
* defaults `metalness` to 0 rather than the engine's 1, because those two defaults have to be
|
|
5098
|
+
* chosen together - the engine's 1 is unreachable under its own `useMetalness` of false, and with
|
|
5099
|
+
* the workflow on it would make every material fully metallic, so `<pc-material diffuse="crimson">`
|
|
5100
|
+
* would render as dark tinted reflections of an environment that may not exist rather than as a
|
|
5101
|
+
* crimson surface. `metalness="1"` remains one attribute away.
|
|
5102
|
+
*
|
|
5103
|
+
* The `roughness` and `roughness-map` attributes are aliases for `gloss` and `gloss-map` that
|
|
4731
5104
|
* additionally invert the gloss channel; do not mix the two families on one element.
|
|
4732
5105
|
*
|
|
4733
5106
|
* The two aliases are documented here rather than on an accessor, because they resolve to the
|
|
@@ -4787,7 +5160,7 @@
|
|
|
4787
5160
|
_heightMapRotation = 0;
|
|
4788
5161
|
_heightMapTiling = new playcanvas.Vec2(1, 1);
|
|
4789
5162
|
_heightMapUv = 0;
|
|
4790
|
-
_metalness =
|
|
5163
|
+
_metalness = 0;
|
|
4791
5164
|
_metalnessMap = '';
|
|
4792
5165
|
_metalnessMapChannel = 'g';
|
|
4793
5166
|
_metalnessMapOffset = new playcanvas.Vec2(0, 0);
|
|
@@ -6730,7 +7103,7 @@
|
|
|
6730
7103
|
this.heightMapUv = parseNumber(newValue, 0, name);
|
|
6731
7104
|
break;
|
|
6732
7105
|
case 'metalness':
|
|
6733
|
-
this.metalness = parseNumber(newValue,
|
|
7106
|
+
this.metalness = parseNumber(newValue, 0, name);
|
|
6734
7107
|
break;
|
|
6735
7108
|
case 'metalness-map':
|
|
6736
7109
|
this.metalnessMap = newValue ?? '';
|
|
@@ -7165,6 +7538,14 @@
|
|
|
7165
7538
|
}
|
|
7166
7539
|
customElements.define('pc-rigidbody', RigidBodyComponentElement);
|
|
7167
7540
|
|
|
7541
|
+
// The engine's SCALEMODE_* constants are the strings 'none' and 'blend', so this map happens to be
|
|
7542
|
+
// an identity. It is still the right shape: it supplies parseEnum's valid-name list, it is what the
|
|
7543
|
+
// manifest generator reads the enum values from, and it keeps the attribute vocabulary independent
|
|
7544
|
+
// of constants the engine is free to change.
|
|
7545
|
+
const scaleModes = new Map([
|
|
7546
|
+
['none', playcanvas.SCALEMODE_NONE],
|
|
7547
|
+
['blend', playcanvas.SCALEMODE_BLEND]
|
|
7548
|
+
]);
|
|
7168
7549
|
/**
|
|
7169
7550
|
* The ScreenComponentElement interface provides properties and methods for manipulating
|
|
7170
7551
|
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-screen/ | `<pc-screen>`} elements.
|
|
@@ -7178,7 +7559,7 @@
|
|
|
7178
7559
|
_resolution = new playcanvas.Vec2(640, 320);
|
|
7179
7560
|
_referenceResolution = new playcanvas.Vec2(640, 320);
|
|
7180
7561
|
_priority = 0;
|
|
7181
|
-
|
|
7562
|
+
_scaleMode = 'none';
|
|
7182
7563
|
_scaleBlend = 0.5;
|
|
7183
7564
|
/** @ignore */
|
|
7184
7565
|
constructor() {
|
|
@@ -7190,7 +7571,7 @@
|
|
|
7190
7571
|
referenceResolution: this._referenceResolution,
|
|
7191
7572
|
resolution: this._resolution,
|
|
7192
7573
|
scaleBlend: this._scaleBlend,
|
|
7193
|
-
scaleMode: this.
|
|
7574
|
+
scaleMode: scaleModes.get(this._scaleMode) ?? playcanvas.SCALEMODE_NONE,
|
|
7194
7575
|
screenSpace: this._screenSpace
|
|
7195
7576
|
};
|
|
7196
7577
|
}
|
|
@@ -7228,23 +7609,44 @@
|
|
|
7228
7609
|
get resolution() {
|
|
7229
7610
|
return this._resolution;
|
|
7230
7611
|
}
|
|
7612
|
+
/**
|
|
7613
|
+
* Sets how the screen's `resolution` and `referenceResolution` are weighted against each other
|
|
7614
|
+
* when `scaleMode` is `blend`, from 0 (follow the resolution) to 1 (follow the reference
|
|
7615
|
+
* resolution). Ignored while `scaleMode` is `none`.
|
|
7616
|
+
* @param value - The scale blend factor.
|
|
7617
|
+
*/
|
|
7231
7618
|
set scaleBlend(value) {
|
|
7232
7619
|
this._scaleBlend = value;
|
|
7233
7620
|
if (this.component) {
|
|
7234
7621
|
this.component.scaleBlend = this._scaleBlend;
|
|
7235
7622
|
}
|
|
7236
7623
|
}
|
|
7624
|
+
/**
|
|
7625
|
+
* Gets how the screen's resolutions are weighted against each other.
|
|
7626
|
+
* @returns The scale blend factor.
|
|
7627
|
+
*/
|
|
7237
7628
|
get scaleBlend() {
|
|
7238
7629
|
return this._scaleBlend;
|
|
7239
7630
|
}
|
|
7240
|
-
|
|
7241
|
-
|
|
7631
|
+
/**
|
|
7632
|
+
* Sets how the screen scales its contents. `none` renders at `resolution` and ignores
|
|
7633
|
+
* `referenceResolution`; `blend` scales between the two, weighted by `scaleBlend`, which is what
|
|
7634
|
+
* keeps a UI laid out at one resolution usable at another. Requires `screenSpace` - the engine
|
|
7635
|
+
* forces `none` on a world-space screen, which does not support scaling.
|
|
7636
|
+
* @param value - The scale mode ('none' or 'blend').
|
|
7637
|
+
*/
|
|
7638
|
+
set scaleMode(value) {
|
|
7639
|
+
this._scaleMode = value;
|
|
7242
7640
|
if (this.component) {
|
|
7243
|
-
this.component.scaleMode =
|
|
7641
|
+
this.component.scaleMode = scaleModes.get(value) ?? playcanvas.SCALEMODE_NONE;
|
|
7244
7642
|
}
|
|
7245
7643
|
}
|
|
7246
|
-
|
|
7247
|
-
|
|
7644
|
+
/**
|
|
7645
|
+
* Gets how the screen scales its contents.
|
|
7646
|
+
* @returns The scale mode.
|
|
7647
|
+
*/
|
|
7648
|
+
get scaleMode() {
|
|
7649
|
+
return this._scaleMode;
|
|
7248
7650
|
}
|
|
7249
7651
|
set screenSpace(value) {
|
|
7250
7652
|
this._screenSpace = value;
|
|
@@ -7258,12 +7660,12 @@
|
|
|
7258
7660
|
static get observedAttributes() {
|
|
7259
7661
|
return [
|
|
7260
7662
|
...super.observedAttributes,
|
|
7261
|
-
'blend',
|
|
7262
7663
|
'screen-space',
|
|
7263
7664
|
'resolution',
|
|
7264
7665
|
'reference-resolution',
|
|
7265
7666
|
'priority',
|
|
7266
|
-
'scale-blend'
|
|
7667
|
+
'scale-blend',
|
|
7668
|
+
'scale-mode'
|
|
7267
7669
|
];
|
|
7268
7670
|
}
|
|
7269
7671
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
@@ -7281,8 +7683,8 @@
|
|
|
7281
7683
|
case 'scale-blend':
|
|
7282
7684
|
this.scaleBlend = parseNumber(newValue, 0.5, name);
|
|
7283
7685
|
break;
|
|
7284
|
-
case '
|
|
7285
|
-
this.
|
|
7686
|
+
case 'scale-mode':
|
|
7687
|
+
this.scaleMode = parseEnum(newValue, scaleModes, 'none', name);
|
|
7286
7688
|
break;
|
|
7287
7689
|
case 'screen-space':
|
|
7288
7690
|
this.screenSpace = parseBool(newValue, false);
|
|
@@ -7505,7 +7907,8 @@
|
|
|
7505
7907
|
return super.component;
|
|
7506
7908
|
}
|
|
7507
7909
|
/**
|
|
7508
|
-
* Sets whether horizontal
|
|
7910
|
+
* Sets whether scrolling along the horizontal axis is enabled. This is a toggle, unlike the
|
|
7911
|
+
* `orientation` of a `<pc-scrollbar>`, for which `horizontal` is one of the accepted values.
|
|
7509
7912
|
* @param value - Whether horizontal scrolling is enabled.
|
|
7510
7913
|
*/
|
|
7511
7914
|
set horizontal(value) {
|
|
@@ -7515,14 +7918,15 @@
|
|
|
7515
7918
|
}
|
|
7516
7919
|
}
|
|
7517
7920
|
/**
|
|
7518
|
-
* Gets whether horizontal
|
|
7921
|
+
* Gets whether scrolling along the horizontal axis is enabled.
|
|
7519
7922
|
* @returns Whether horizontal scrolling is enabled.
|
|
7520
7923
|
*/
|
|
7521
7924
|
get horizontal() {
|
|
7522
7925
|
return this._horizontal;
|
|
7523
7926
|
}
|
|
7524
7927
|
/**
|
|
7525
|
-
* Sets whether vertical
|
|
7928
|
+
* Sets whether scrolling along the vertical axis is enabled. This is a toggle, unlike the
|
|
7929
|
+
* `orientation` of a `<pc-scrollbar>`, for which `vertical` is one of the accepted values.
|
|
7526
7930
|
* @param value - Whether vertical scrolling is enabled.
|
|
7527
7931
|
*/
|
|
7528
7932
|
set vertical(value) {
|
|
@@ -7532,7 +7936,7 @@
|
|
|
7532
7936
|
}
|
|
7533
7937
|
}
|
|
7534
7938
|
/**
|
|
7535
|
-
* Gets whether vertical
|
|
7939
|
+
* Gets whether scrolling along the vertical axis is enabled.
|
|
7536
7940
|
* @returns Whether vertical scrolling is enabled.
|
|
7537
7941
|
*/
|
|
7538
7942
|
get vertical() {
|
|
@@ -9328,30 +9732,58 @@
|
|
|
9328
9732
|
_gravity = new playcanvas.Vec3(0, -9.81, 0);
|
|
9329
9733
|
_scene = null;
|
|
9330
9734
|
/**
|
|
9331
|
-
* The PlayCanvas scene instance.
|
|
9735
|
+
* The PlayCanvas scene instance. `null` until the element is ready — await
|
|
9332
9736
|
* {@link whenReady} or the element's `ready()` promise before accessing it.
|
|
9333
|
-
* @returns The scene instance
|
|
9737
|
+
* @returns The scene instance, or `null`.
|
|
9334
9738
|
*/
|
|
9335
9739
|
get scene() {
|
|
9336
9740
|
return this._scene;
|
|
9337
9741
|
}
|
|
9338
9742
|
async connectedCallback() {
|
|
9339
|
-
|
|
9340
|
-
|
|
9743
|
+
const appElement = this.closestApp;
|
|
9744
|
+
if (!appElement) {
|
|
9745
|
+
console.warn('pc-scene must be a descendant of pc-app - scene settings not applied');
|
|
9746
|
+
return;
|
|
9747
|
+
}
|
|
9748
|
+
await appElement.ready();
|
|
9749
|
+
// The element may have been removed or re-parented while waiting for the app. Matches the
|
|
9750
|
+
// guard in AssetElement and MaterialElement, but compares closestApp rather than
|
|
9751
|
+
// parentElement because pc-scene resolves its app by ancestor rather than direct child.
|
|
9752
|
+
// Without this, a scene re-parented mid-await would take its Scene from the app it started
|
|
9753
|
+
// under while _applyGravity resolved the app it ended up under, splitting the two.
|
|
9754
|
+
if (!this.isConnected || this.closestApp !== appElement) {
|
|
9755
|
+
return;
|
|
9756
|
+
}
|
|
9757
|
+
// The application is gone if the tree was torn down while we awaited readiness. There is
|
|
9758
|
+
// nothing to configure and nothing the author can act on, so this stays silent.
|
|
9759
|
+
const app = appElement.app;
|
|
9760
|
+
if (!app) {
|
|
9761
|
+
return;
|
|
9762
|
+
}
|
|
9763
|
+
this._scene = app.scene;
|
|
9341
9764
|
this.updateSceneSettings();
|
|
9342
9765
|
this._onReady();
|
|
9343
9766
|
}
|
|
9344
9767
|
updateSceneSettings() {
|
|
9345
|
-
if (this.
|
|
9346
|
-
this.
|
|
9347
|
-
this.
|
|
9348
|
-
this.
|
|
9349
|
-
this.
|
|
9350
|
-
this.
|
|
9351
|
-
|
|
9352
|
-
appElement.app.systems.rigidbody.gravity.copy(this._gravity);
|
|
9768
|
+
if (this._scene) {
|
|
9769
|
+
this._scene.fog.type = this._fog;
|
|
9770
|
+
this._scene.fog.color = this._fogColor;
|
|
9771
|
+
this._scene.fog.density = this._fogDensity;
|
|
9772
|
+
this._scene.fog.start = this._fogStart;
|
|
9773
|
+
this._scene.fog.end = this._fogEnd;
|
|
9774
|
+
this._applyGravity(this._gravity);
|
|
9353
9775
|
}
|
|
9354
9776
|
}
|
|
9777
|
+
/**
|
|
9778
|
+
* Applies gravity to the rigid body system. Resolved through `closestApp` rather than
|
|
9779
|
+
* `parentElement` so that a `<pc-scene>` nested inside a wrapper element behaves the same as
|
|
9780
|
+
* a direct child, matching how `connectedCallback` resolves the application.
|
|
9781
|
+
*
|
|
9782
|
+
* @param value - The gravity to apply.
|
|
9783
|
+
*/
|
|
9784
|
+
_applyGravity(value) {
|
|
9785
|
+
this.closestApp?.app?.systems.rigidbody?.gravity.copy(value);
|
|
9786
|
+
}
|
|
9355
9787
|
/**
|
|
9356
9788
|
* Sets the fog type of the scene. Can be `none`, `linear`, `exp` or `exp2`. Defaults to
|
|
9357
9789
|
* `none`.
|
|
@@ -9444,9 +9876,8 @@
|
|
|
9444
9876
|
*/
|
|
9445
9877
|
set gravity(value) {
|
|
9446
9878
|
this._gravity = value;
|
|
9447
|
-
if (this.
|
|
9448
|
-
|
|
9449
|
-
appElement.app.systems.rigidbody.gravity.copy(value);
|
|
9879
|
+
if (this._scene) {
|
|
9880
|
+
this._applyGravity(value);
|
|
9450
9881
|
}
|
|
9451
9882
|
}
|
|
9452
9883
|
/**
|
|
@@ -9495,7 +9926,7 @@
|
|
|
9495
9926
|
_center = new playcanvas.Vec3(0, 0.01, 0);
|
|
9496
9927
|
_intensity = 1;
|
|
9497
9928
|
_rotation = new playcanvas.Vec3();
|
|
9498
|
-
|
|
9929
|
+
_mipLevel = 0;
|
|
9499
9930
|
_lighting = false;
|
|
9500
9931
|
_scale = new playcanvas.Vec3(100, 100, 100);
|
|
9501
9932
|
_type = 'infinite';
|
|
@@ -9529,7 +9960,7 @@
|
|
|
9529
9960
|
this._scene.sky.node.setLocalScale(this._scale);
|
|
9530
9961
|
this._scene.sky.center = this._center;
|
|
9531
9962
|
this._scene.skyboxIntensity = this._intensity;
|
|
9532
|
-
this._scene.skyboxMip = this.
|
|
9963
|
+
this._scene.skyboxMip = this._mipLevel;
|
|
9533
9964
|
}
|
|
9534
9965
|
async _loadSkybox() {
|
|
9535
9966
|
const appElement = await this.closestApp?.ready();
|
|
@@ -9621,23 +10052,6 @@
|
|
|
9621
10052
|
get intensity() {
|
|
9622
10053
|
return this._intensity;
|
|
9623
10054
|
}
|
|
9624
|
-
/**
|
|
9625
|
-
* Sets the mip level of the skybox.
|
|
9626
|
-
* @param value - The mip level.
|
|
9627
|
-
*/
|
|
9628
|
-
set level(value) {
|
|
9629
|
-
this._level = value;
|
|
9630
|
-
if (this._scene) {
|
|
9631
|
-
this._scene.skyboxMip = this._level;
|
|
9632
|
-
}
|
|
9633
|
-
}
|
|
9634
|
-
/**
|
|
9635
|
-
* Gets the mip level of the skybox.
|
|
9636
|
-
* @returns The mip level.
|
|
9637
|
-
*/
|
|
9638
|
-
get level() {
|
|
9639
|
-
return this._level;
|
|
9640
|
-
}
|
|
9641
10055
|
/**
|
|
9642
10056
|
* Sets whether the skybox is used as a light source.
|
|
9643
10057
|
* @param value - Whether to use lighting.
|
|
@@ -9652,6 +10066,24 @@
|
|
|
9652
10066
|
get lighting() {
|
|
9653
10067
|
return this._lighting;
|
|
9654
10068
|
}
|
|
10069
|
+
/**
|
|
10070
|
+
* Sets the mip level of the skybox, where 0 is the sharpest. Raising it selects a blurrier mip,
|
|
10071
|
+
* which is how a skybox is softened without blurring the texture itself.
|
|
10072
|
+
* @param value - The mip level.
|
|
10073
|
+
*/
|
|
10074
|
+
set mipLevel(value) {
|
|
10075
|
+
this._mipLevel = value;
|
|
10076
|
+
if (this._scene) {
|
|
10077
|
+
this._scene.skyboxMip = this._mipLevel;
|
|
10078
|
+
}
|
|
10079
|
+
}
|
|
10080
|
+
/**
|
|
10081
|
+
* Gets the mip level of the skybox.
|
|
10082
|
+
* @returns The mip level.
|
|
10083
|
+
*/
|
|
10084
|
+
get mipLevel() {
|
|
10085
|
+
return this._mipLevel;
|
|
10086
|
+
}
|
|
9655
10087
|
/**
|
|
9656
10088
|
* Sets the Euler rotation of the skybox.
|
|
9657
10089
|
* @param value - The rotation.
|
|
@@ -9708,7 +10140,7 @@
|
|
|
9708
10140
|
return this._type;
|
|
9709
10141
|
}
|
|
9710
10142
|
static get observedAttributes() {
|
|
9711
|
-
return ['asset', 'center', 'intensity', '
|
|
10143
|
+
return ['asset', 'center', 'intensity', 'lighting', 'mip-level', 'rotation', 'scale', 'type'];
|
|
9712
10144
|
}
|
|
9713
10145
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
9714
10146
|
switch (name) {
|
|
@@ -9721,12 +10153,12 @@
|
|
|
9721
10153
|
case 'intensity':
|
|
9722
10154
|
this.intensity = parseNumber(newValue, 1, name);
|
|
9723
10155
|
break;
|
|
9724
|
-
case 'level':
|
|
9725
|
-
this.level = parseNumber(newValue, 0, name);
|
|
9726
|
-
break;
|
|
9727
10156
|
case 'lighting':
|
|
9728
10157
|
this.lighting = parseBool(newValue, false);
|
|
9729
10158
|
break;
|
|
10159
|
+
case 'mip-level':
|
|
10160
|
+
this.mipLevel = parseNumber(newValue, 0, name);
|
|
10161
|
+
break;
|
|
9730
10162
|
case 'rotation':
|
|
9731
10163
|
this.rotation = parseVec3(newValue, playcanvas.Vec3.ZERO, name);
|
|
9732
10164
|
break;
|