@playcanvas/web-components 0.10.1 → 0.11.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.ts +102 -51
- package/dist/asset.d.ts +8 -7
- package/dist/async-element.d.ts +21 -5
- package/dist/components/button-component.d.ts +3 -7
- package/dist/components/camera-component.d.ts +16 -20
- package/dist/components/collision-component.d.ts +3 -7
- package/dist/components/component.d.ts +22 -4
- package/dist/components/element-component.d.ts +4 -8
- package/dist/components/gsplat-component.d.ts +2 -7
- package/dist/components/layoutchild-component.d.ts +2 -7
- package/dist/components/layoutgroup-component.d.ts +3 -7
- package/dist/components/light-component.d.ts +3 -7
- package/dist/components/listener-component.d.ts +1 -6
- package/dist/components/particlesystem-component.d.ts +2 -7
- package/dist/components/render-component.d.ts +2 -7
- package/dist/components/rigidbody-component.d.ts +3 -7
- package/dist/components/screen-component.d.ts +28 -11
- package/dist/components/script-component.d.ts +8 -20
- package/dist/components/script.d.ts +2 -22
- package/dist/components/scrollbar-component.d.ts +2 -7
- package/dist/components/scrollview-component.d.ts +9 -11
- package/dist/components/sound-component.d.ts +2 -7
- package/dist/components/sound-slot.d.ts +8 -6
- package/dist/custom-elements.json +5191 -10695
- package/dist/entity.d.ts +16 -9
- package/dist/index.d.ts +37 -0
- package/dist/material.d.ts +12 -12
- package/dist/model.d.ts +21 -5
- package/dist/module.d.ts +0 -6
- package/dist/parse.d.ts +6 -3
- package/dist/pwc.cjs +799 -287
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +799 -287
- 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 +800 -288
- package/dist/pwc.mjs.map +1 -1
- package/dist/scene.d.ts +4 -7
- package/dist/sky.d.ts +25 -16
- package/dist/vscode.html-custom-data.json +65 -45
- package/dist/web-types.json +585 -557
- package/package.json +8 -7
- package/src/app.ts +326 -144
- package/src/asset.ts +34 -28
- package/src/async-element.ts +34 -8
- package/src/components/button-component.ts +5 -9
- package/src/components/camera-component.ts +55 -36
- package/src/components/collision-component.ts +26 -15
- package/src/components/component.ts +58 -8
- package/src/components/element-component.ts +26 -30
- package/src/components/gsplat-component.ts +4 -9
- package/src/components/layoutchild-component.ts +4 -9
- package/src/components/layoutgroup-component.ts +14 -9
- package/src/components/light-component.ts +42 -12
- package/src/components/listener-component.ts +1 -7
- package/src/components/particlesystem-component.ts +7 -15
- package/src/components/render-component.ts +5 -10
- package/src/components/rigidbody-component.ts +23 -16
- package/src/components/screen-component.ts +46 -20
- package/src/components/script-component.ts +108 -46
- package/src/components/script.ts +38 -33
- package/src/components/scrollbar-component.ts +6 -16
- package/src/components/scrollview-component.ts +22 -15
- package/src/components/sound-component.ts +10 -15
- package/src/components/sound-slot.ts +30 -20
- package/src/entity.ts +75 -34
- package/src/index.ts +45 -1
- package/src/loading-bar.ts +8 -8
- package/src/material.ts +63 -37
- package/src/model.ts +69 -14
- package/src/module.ts +8 -7
- package/src/parse.ts +67 -20
- package/src/scene.ts +12 -9
- package/src/sky.ts +76 -34
package/dist/pwc.js
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* Base class for all PlayCanvas Web Components that initialize asynchronously.
|
|
9
9
|
*
|
|
10
|
-
* @fires {CustomEvent} ready - Fired
|
|
11
|
-
*
|
|
10
|
+
* @fires {CustomEvent} ready - Fired when the element is fully initialized — once per readiness
|
|
11
|
+
* cycle, so an element that is torn down and re-initialized (for example by removing and
|
|
12
|
+
* re-inserting it) fires it again. Bubbles and is composed.
|
|
12
13
|
*/
|
|
13
14
|
class AsyncElement extends HTMLElement {
|
|
14
15
|
_readyPromise;
|
|
15
16
|
_readyResolve;
|
|
17
|
+
_readyResolved = false;
|
|
16
18
|
/** @ignore */
|
|
17
19
|
constructor() {
|
|
18
20
|
super();
|
|
@@ -39,15 +41,39 @@
|
|
|
39
41
|
/**
|
|
40
42
|
* Called when the element is fully initialized and ready. Subclasses should call this when
|
|
41
43
|
* they're ready. Resolves the ready promise and dispatches a bubbling, composed `ready`
|
|
42
|
-
* event.
|
|
44
|
+
* event. Signals at most once per readiness cycle: a repeat call before {@link _resetReady}
|
|
45
|
+
* has re-armed the promise does nothing.
|
|
43
46
|
*/
|
|
44
47
|
_onReady() {
|
|
48
|
+
if (this._readyResolved)
|
|
49
|
+
return;
|
|
50
|
+
this._readyResolved = true;
|
|
45
51
|
this._readyResolve();
|
|
46
52
|
this.dispatchEvent(new CustomEvent('ready', { bubbles: true, composed: true }));
|
|
47
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Returns the ready promise to its pending state. Subclasses should call this when the
|
|
56
|
+
* resource their readiness announced is torn down (typically from `disconnectedCallback`),
|
|
57
|
+
* so that a later re-initialization can signal readiness again. Does nothing while the
|
|
58
|
+
* promise is still pending — an in-flight waiter carries over to the next readiness cycle
|
|
59
|
+
* rather than being stranded on a promise nothing will ever resolve.
|
|
60
|
+
*/
|
|
61
|
+
_resetReady() {
|
|
62
|
+
if (!this._readyResolved)
|
|
63
|
+
return;
|
|
64
|
+
this._readyResolved = false;
|
|
65
|
+
this._readyPromise = new Promise((resolve) => {
|
|
66
|
+
this._readyResolve = resolve;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
48
69
|
/**
|
|
49
70
|
* Returns a promise that resolves with this element when it's ready. This is the low-level
|
|
50
71
|
* primitive underlying {@link whenReady}, which is the recommended way to wait for elements.
|
|
72
|
+
*
|
|
73
|
+
* Readiness tracks the element's current lifecycle: once a ready element is torn down (for
|
|
74
|
+
* example by removing it from the document), this returns a fresh promise that resolves when
|
|
75
|
+
* the element is next ready. A promise obtained earlier stays resolved — call this again
|
|
76
|
+
* after re-inserting an element rather than reusing a promise from before its removal.
|
|
51
77
|
* @returns A promise that resolves with this element when it's ready.
|
|
52
78
|
*/
|
|
53
79
|
ready() {
|
|
@@ -122,7 +148,14 @@
|
|
|
122
148
|
});
|
|
123
149
|
}
|
|
124
150
|
}
|
|
125
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Returns the promise that settles when the module has loaded. Awaited by the containing
|
|
153
|
+
* `<pc-app>` element before it creates its graphics device.
|
|
154
|
+
*
|
|
155
|
+
* @returns The load promise.
|
|
156
|
+
* @internal
|
|
157
|
+
*/
|
|
158
|
+
_getLoadPromise() {
|
|
126
159
|
return this.loadPromise;
|
|
127
160
|
}
|
|
128
161
|
}
|
|
@@ -181,10 +214,7 @@
|
|
|
181
214
|
// aria-valuenow is set, which is what marks a progressbar indeterminate. jsdom has no Web
|
|
182
215
|
// Animations API, so the guard degrades to a static bar there rather than crashing boot.
|
|
183
216
|
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
|
-
], {
|
|
217
|
+
this._sweep = this._fill.animate([{ transform: 'scaleX(0.25) translateX(-100%)' }, { transform: 'scaleX(0.25) translateX(500%)' }], {
|
|
188
218
|
duration: 1000,
|
|
189
219
|
iterations: Infinity,
|
|
190
220
|
easing: 'ease-in-out'
|
|
@@ -417,7 +447,7 @@
|
|
|
417
447
|
*/
|
|
418
448
|
const parseComponents = (value, count) => {
|
|
419
449
|
const components = value.trim().split(/\s+/).map(Number);
|
|
420
|
-
if (components.length !== count || components.some(component => !Number.isFinite(component))) {
|
|
450
|
+
if (components.length !== count || components.some((component) => !Number.isFinite(component))) {
|
|
421
451
|
return null;
|
|
422
452
|
}
|
|
423
453
|
return components;
|
|
@@ -473,7 +503,10 @@
|
|
|
473
503
|
if (/^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)) {
|
|
474
504
|
let hex = value.slice(1);
|
|
475
505
|
if (hex.length === 3 || hex.length === 4) {
|
|
476
|
-
hex = hex
|
|
506
|
+
hex = hex
|
|
507
|
+
.split('')
|
|
508
|
+
.map((char) => char + char)
|
|
509
|
+
.join('');
|
|
477
510
|
}
|
|
478
511
|
return new playcanvas.Color().fromString(`#${hex}`);
|
|
479
512
|
}
|
|
@@ -491,7 +524,9 @@
|
|
|
491
524
|
* the value is invalid — the latter also logs a warning listing the valid names.
|
|
492
525
|
*
|
|
493
526
|
* @param value - The attribute value to parse (`null` when the attribute is absent).
|
|
494
|
-
* @param valid - The valid names: an array, or a map whose keys are the valid names.
|
|
527
|
+
* @param valid - The valid names: an array, or a map whose keys are the valid names. Only the keys
|
|
528
|
+
* are read, so the map's value type is unconstrained - engine enums are mostly numeric constants,
|
|
529
|
+
* but some (e.g. `SCALEMODE_BLEND`) are strings.
|
|
495
530
|
* @param defaultValue - The value to use when the attribute is absent or invalid.
|
|
496
531
|
* @param attribute - The attribute name, used in the warning message.
|
|
497
532
|
* @returns The resolved enum name.
|
|
@@ -568,7 +603,10 @@
|
|
|
568
603
|
// caller's default, or a later mutation would write back through it.
|
|
569
604
|
return [...defaultValue];
|
|
570
605
|
}
|
|
571
|
-
return value
|
|
606
|
+
return value
|
|
607
|
+
.split(',')
|
|
608
|
+
.map((tag) => tag.trim())
|
|
609
|
+
.filter((tag) => tag !== '');
|
|
572
610
|
};
|
|
573
611
|
/**
|
|
574
612
|
* Parse a Vec2 attribute value. The expected format is 2 space-separated numbers (e.g. '1 2').
|
|
@@ -660,6 +698,8 @@
|
|
|
660
698
|
return element?.entity ?? null;
|
|
661
699
|
};
|
|
662
700
|
|
|
701
|
+
/** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
|
|
702
|
+
const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'];
|
|
663
703
|
/**
|
|
664
704
|
* The AppElement interface provides properties and methods for manipulating
|
|
665
705
|
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.
|
|
@@ -679,12 +719,37 @@
|
|
|
679
719
|
_alpha = true;
|
|
680
720
|
_backend = 'webgpu';
|
|
681
721
|
_antialias = true;
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
722
|
+
_depthBuffer = true;
|
|
723
|
+
_stencilBuffer = true;
|
|
724
|
+
_maxPixelRatio = Infinity;
|
|
685
725
|
_loadingBar = true;
|
|
726
|
+
/**
|
|
727
|
+
* Set once the graphics options above have been handed to `createGraphicsDevice`, after which
|
|
728
|
+
* writing any of them changes nothing. Guards the warning in {@link _warnIfBooted}, and is
|
|
729
|
+
* cleared on disconnect so a re-connected element boots from its current attributes.
|
|
730
|
+
*/
|
|
731
|
+
_optionsLocked = false;
|
|
686
732
|
_bar = null;
|
|
733
|
+
/**
|
|
734
|
+
* Whether the application has created its initial entity hierarchy. Read by EntityElement to
|
|
735
|
+
* decide whether a newly connected element must create its entity itself or leave it to the
|
|
736
|
+
* boot sweep.
|
|
737
|
+
* @internal
|
|
738
|
+
*/
|
|
687
739
|
_hierarchyReady = false;
|
|
740
|
+
/**
|
|
741
|
+
* Incremented on every connect and disconnect. Boot captures the value on entry and abandons
|
|
742
|
+
* itself wherever it resumes from an await if the value has moved on — so a boot whose
|
|
743
|
+
* element was removed cannot complete against a torn-down element, and a boot whose element
|
|
744
|
+
* was removed and re-inserted (which starts a boot of its own) cannot race the newer one.
|
|
745
|
+
*/
|
|
746
|
+
_bootGeneration = 0;
|
|
747
|
+
/**
|
|
748
|
+
* The elements backing this application's entities, keyed by the entity itself. Registered
|
|
749
|
+
* by EntityElement at creation and removed when an entity is destroyed, this joins engine
|
|
750
|
+
* scene nodes back to their owning elements by identity - never by name.
|
|
751
|
+
*/
|
|
752
|
+
_entityElements = new Map();
|
|
688
753
|
_picker = null;
|
|
689
754
|
_hasPointerListeners = {
|
|
690
755
|
pointerenter: false,
|
|
@@ -731,8 +796,16 @@
|
|
|
731
796
|
super();
|
|
732
797
|
// Bind methods to maintain 'this' context
|
|
733
798
|
this._onWindowResize = this._onWindowResize.bind(this);
|
|
799
|
+
// Track pointer listeners being added to and removed from descendant entities.
|
|
800
|
+
// Registered once here rather than on every boot - the handlers no-op while there is no
|
|
801
|
+
// canvas, and a re-booted element must not stack a second set.
|
|
802
|
+
pointerEventTypes.forEach((type) => {
|
|
803
|
+
this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type));
|
|
804
|
+
this.addEventListener(`${type}:disconnect`, () => this._onPointerListenerRemoved(type));
|
|
805
|
+
});
|
|
734
806
|
}
|
|
735
807
|
async connectedCallback() {
|
|
808
|
+
const generation = ++this._bootGeneration;
|
|
736
809
|
// Created before the first await, so the bar is visible while modules and the graphics
|
|
737
810
|
// device are created, and exists before any disconnect could need to clean it up
|
|
738
811
|
if (this._loadingBar && !this._bar) {
|
|
@@ -741,7 +814,12 @@
|
|
|
741
814
|
// Get all pc-module elements that are direct children of the pc-app element
|
|
742
815
|
const moduleElements = this.querySelectorAll(':scope > pc-module');
|
|
743
816
|
// Wait for all modules to load
|
|
744
|
-
await Promise.all(Array.from(moduleElements).map(module => module.
|
|
817
|
+
await Promise.all(Array.from(moduleElements).map((module) => module._getLoadPromise()));
|
|
818
|
+
// The element may have been removed while the modules loaded. Nothing beyond the loading
|
|
819
|
+
// bar exists yet, and disconnectedCallback has already destroyed that.
|
|
820
|
+
if (generation !== this._bootGeneration) {
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
745
823
|
// Create and append the canvas to the element
|
|
746
824
|
this._canvas = document.createElement('canvas');
|
|
747
825
|
this.appendChild(this._canvas);
|
|
@@ -752,15 +830,26 @@
|
|
|
752
830
|
null: ['null']
|
|
753
831
|
};
|
|
754
832
|
const deviceTypes = backendToDeviceTypes[this._backend] || [];
|
|
833
|
+
this._optionsLocked = true;
|
|
755
834
|
const device = await playcanvas.createGraphicsDevice(this._canvas, {
|
|
756
835
|
// @ts-ignore - alpha needs to be documented
|
|
757
836
|
alpha: this._alpha,
|
|
758
837
|
antialias: this._antialias,
|
|
759
|
-
depth: this.
|
|
838
|
+
depth: this._depthBuffer,
|
|
760
839
|
deviceTypes: deviceTypes,
|
|
761
|
-
stencil: this.
|
|
840
|
+
stencil: this._stencilBuffer
|
|
762
841
|
});
|
|
763
|
-
|
|
842
|
+
// The element may have been removed while the device was created. disconnectedCallback
|
|
843
|
+
// has already cleaned up the canvas; the device was created inside the await, so it is
|
|
844
|
+
// this boot's to release.
|
|
845
|
+
if (generation !== this._bootGeneration) {
|
|
846
|
+
device.destroy();
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
// Assigned rather than resolved to a number here: the engine caps against the live
|
|
850
|
+
// window.devicePixelRatio on every resize, so an uncapped Infinity keeps following the
|
|
851
|
+
// display when a window moves between monitors of differing density.
|
|
852
|
+
device.maxPixelRatio = this._maxPixelRatio;
|
|
764
853
|
const createOptions = new playcanvas.AppOptions();
|
|
765
854
|
createOptions.graphicsDevice = device;
|
|
766
855
|
createOptions.keyboard = new playcanvas.Keyboard(window);
|
|
@@ -833,27 +922,41 @@
|
|
|
833
922
|
this._pickerCreate();
|
|
834
923
|
// Get all pc-asset elements that are direct children of the pc-app element
|
|
835
924
|
const assetElements = this.querySelectorAll(':scope > pc-asset');
|
|
836
|
-
Array.from(assetElements)
|
|
837
|
-
assetElement.
|
|
925
|
+
for (const assetElement of Array.from(assetElements)) {
|
|
926
|
+
assetElement._createAsset();
|
|
838
927
|
const asset = assetElement.asset;
|
|
839
928
|
if (asset) {
|
|
840
929
|
app.assets.add(asset);
|
|
930
|
+
// Adding a fileless asset (one built purely from data, such as a sprite)
|
|
931
|
+
// completes it synchronously, dispatching the element's load event - whose
|
|
932
|
+
// listeners may have removed this element. Stop before the next addition
|
|
933
|
+
// reaches the destroyed registry, and before orphan entities are created.
|
|
934
|
+
if (generation !== this._bootGeneration) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
841
937
|
}
|
|
842
|
-
}
|
|
938
|
+
}
|
|
843
939
|
// Get all pc-material elements that are direct children of the pc-app element
|
|
844
940
|
const materialElements = this.querySelectorAll(':scope > pc-material');
|
|
845
941
|
Array.from(materialElements).forEach((materialElement) => {
|
|
846
|
-
materialElement.
|
|
942
|
+
materialElement._createMaterial();
|
|
847
943
|
});
|
|
848
944
|
// Create all entities
|
|
849
945
|
const entityElements = this.querySelectorAll('pc-entity');
|
|
850
946
|
Array.from(entityElements).forEach((entityElement) => {
|
|
851
|
-
entityElement.
|
|
947
|
+
entityElement._createEntity(app);
|
|
852
948
|
});
|
|
853
949
|
// Build hierarchy
|
|
854
950
|
entityElements.forEach((entityElement) => {
|
|
855
|
-
entityElement.
|
|
951
|
+
entityElement._buildHierarchy(app);
|
|
856
952
|
});
|
|
953
|
+
// Building the hierarchy dispatched each entity's ready event synchronously, and a
|
|
954
|
+
// listener may have removed the element. The sweep itself degrades safely - destroying
|
|
955
|
+
// the application nulls every element's entity, so the remaining builds no-op - but the
|
|
956
|
+
// teardown's reset must not be overwritten here.
|
|
957
|
+
if (generation !== this._bootGeneration) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
857
960
|
this._hierarchyReady = true;
|
|
858
961
|
// Forward the engine's preload lifecycle as DOM ProgressEvents on this element. The
|
|
859
962
|
// listener must be attached before preload() is called: an asset that is already loaded
|
|
@@ -870,8 +973,19 @@
|
|
|
870
973
|
this._loadProgress = total === 0 ? 1 : 0;
|
|
871
974
|
this._bar?.progress(0, total);
|
|
872
975
|
this.dispatchEvent(new ProgressEvent('progress', { lengthComputable: true, loaded: 0, total }));
|
|
976
|
+
// The progress dispatch above ran listeners synchronously, and one may have removed the
|
|
977
|
+
// element. The application is already destroyed - it must not be asked to preload.
|
|
978
|
+
if (generation !== this._bootGeneration) {
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
873
981
|
// Load assets before starting the application
|
|
874
982
|
app.preload(() => {
|
|
983
|
+
// The element may have been removed while assets loaded. The application is already
|
|
984
|
+
// destroyed, so it must not be started — and readiness must not be signaled for a
|
|
985
|
+
// boot that no longer owns the element.
|
|
986
|
+
if (generation !== this._bootGeneration) {
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
875
989
|
// Scope the counter to this preload pass, so a later app.preload() call by user code
|
|
876
990
|
// cannot push `loaded` past `total`
|
|
877
991
|
app.off('preload:progress', onPreloadProgress);
|
|
@@ -887,15 +1001,26 @@
|
|
|
887
1001
|
});
|
|
888
1002
|
}
|
|
889
1003
|
disconnectedCallback() {
|
|
1004
|
+
// Invalidate any boot still in flight, so it abandons itself when it next resumes
|
|
1005
|
+
// instead of completing against a torn-down element.
|
|
1006
|
+
this._bootGeneration++;
|
|
1007
|
+
this._optionsLocked = false;
|
|
890
1008
|
this._pickerDestroy();
|
|
891
|
-
// Clean up the application
|
|
1009
|
+
// Clean up the application. Destroying it destroys every entity, whose destroy hooks
|
|
1010
|
+
// unregister them - clear() covers any entity the engine no longer reached.
|
|
892
1011
|
if (this._app) {
|
|
893
1012
|
this._app.destroy();
|
|
894
1013
|
this._app = null;
|
|
895
1014
|
}
|
|
1015
|
+
this._entityElements.clear();
|
|
896
1016
|
this._loadProgress = 0;
|
|
897
1017
|
this._bar?.destroy();
|
|
898
1018
|
this._bar = null;
|
|
1019
|
+
// Return the element to its pre-boot state, so re-inserting it boots afresh: descendants
|
|
1020
|
+
// must neither see a hierarchy that no longer exists nor resume against a readiness that
|
|
1021
|
+
// no longer holds.
|
|
1022
|
+
this._hierarchyReady = false;
|
|
1023
|
+
this._resetReady();
|
|
899
1024
|
// Remove event listeners
|
|
900
1025
|
window.removeEventListener('resize', this._onWindowResize);
|
|
901
1026
|
// Remove the canvas
|
|
@@ -923,14 +1048,11 @@
|
|
|
923
1048
|
this._pointerHandlers.pointermove = listener(this._onPointerMove);
|
|
924
1049
|
this._pointerHandlers.pointerdown = listener(this._onPointerDown);
|
|
925
1050
|
this._pointerHandlers.pointerup = listener(this._onPointerUp);
|
|
926
|
-
//
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
// created from onpointer* attributes when their elements were first upgraded)
|
|
932
|
-
const anyListeners = Array.from(this.querySelectorAll('pc-entity'))
|
|
933
|
-
.some(entity => entity.hasListeners(type));
|
|
1051
|
+
// Attach canvas handlers for listeners registered before this boot (e.g. handlers
|
|
1052
|
+
// created from onpointer* attributes when their elements were first upgraded, or
|
|
1053
|
+
// listeners carried over from before a re-boot)
|
|
1054
|
+
pointerEventTypes.forEach((type) => {
|
|
1055
|
+
const anyListeners = Array.from(this.querySelectorAll('pc-entity')).some((entity) => entity._hasListeners(type));
|
|
934
1056
|
if (anyListeners) {
|
|
935
1057
|
this._onPointerListenerAdded(type);
|
|
936
1058
|
}
|
|
@@ -945,6 +1067,7 @@
|
|
|
945
1067
|
});
|
|
946
1068
|
}
|
|
947
1069
|
this._picker = null;
|
|
1070
|
+
this._hoveredEntity = null;
|
|
948
1071
|
this._pointerHandlers = {
|
|
949
1072
|
pointermove: null,
|
|
950
1073
|
pointerdown: null,
|
|
@@ -958,6 +1081,74 @@
|
|
|
958
1081
|
pointermove: false
|
|
959
1082
|
};
|
|
960
1083
|
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Registers the element that created an entity. Called by EntityElement when it creates its
|
|
1086
|
+
* entity.
|
|
1087
|
+
*
|
|
1088
|
+
* @param entity - The entity.
|
|
1089
|
+
* @param element - The element that created it.
|
|
1090
|
+
* @internal
|
|
1091
|
+
*/
|
|
1092
|
+
_registerEntityElement(entity, element) {
|
|
1093
|
+
this._entityElements.set(entity, element);
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Removes the registration for a destroyed entity. Called by EntityElement.
|
|
1097
|
+
*
|
|
1098
|
+
* @param entity - The entity.
|
|
1099
|
+
* @internal
|
|
1100
|
+
*/
|
|
1101
|
+
_unregisterEntityElement(entity) {
|
|
1102
|
+
this._entityElements.delete(entity);
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Returns the `<pc-entity>` element whose backing entity is `entity`, or `null` if the
|
|
1106
|
+
* entity was not created by an element of this application - for example, a node inside a
|
|
1107
|
+
* model's instantiated hierarchy, or an entity created through the engine API.
|
|
1108
|
+
*
|
|
1109
|
+
* @param entity - The entity to look up.
|
|
1110
|
+
* @returns The element backing the entity, or `null`.
|
|
1111
|
+
*/
|
|
1112
|
+
elementFromEntity(entity) {
|
|
1113
|
+
return this._entityElements.get(entity) ?? null;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Resolves the element that owns a picked node: the nearest node up the parent chain -
|
|
1117
|
+
* starting with the node itself - that was created by a `<pc-entity>` of this application.
|
|
1118
|
+
* A hit inside a model's instantiated hierarchy therefore resolves to the element hosting
|
|
1119
|
+
* the model.
|
|
1120
|
+
*
|
|
1121
|
+
* @param node - The picked node, or `null`.
|
|
1122
|
+
* @returns The owning element, or `null`.
|
|
1123
|
+
*/
|
|
1124
|
+
_elementFromNode(node) {
|
|
1125
|
+
while (node !== null) {
|
|
1126
|
+
const element = this._entityElements.get(node);
|
|
1127
|
+
if (element) {
|
|
1128
|
+
return element;
|
|
1129
|
+
}
|
|
1130
|
+
node = node.parent;
|
|
1131
|
+
}
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Like {@link _elementFromNode}, but skips elements without a listener for `type`, so a hit
|
|
1136
|
+
* on an unlistened child still reaches a listening ancestor.
|
|
1137
|
+
*
|
|
1138
|
+
* @param node - The picked node, or `null`.
|
|
1139
|
+
* @param type - The pointer event type a listener is required for.
|
|
1140
|
+
* @returns The nearest listening element, or `null`.
|
|
1141
|
+
*/
|
|
1142
|
+
_elementWithListener(node, type) {
|
|
1143
|
+
while (node !== null) {
|
|
1144
|
+
const element = this._entityElements.get(node);
|
|
1145
|
+
if (element?._hasListeners(type)) {
|
|
1146
|
+
return element;
|
|
1147
|
+
}
|
|
1148
|
+
node = node.parent;
|
|
1149
|
+
}
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
961
1152
|
// New helper to convert CSS coordinates to canvas (picker) coordinates
|
|
962
1153
|
_getPickerCoordinates(event) {
|
|
963
1154
|
// Get the canvas' bounding rectangle in CSS pixels.
|
|
@@ -1003,56 +1194,44 @@
|
|
|
1003
1194
|
const node = await this._pickNode(event);
|
|
1004
1195
|
if (token !== this._pickToken || !this._picker)
|
|
1005
1196
|
return;
|
|
1006
|
-
//
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
while (currentNode !== null) {
|
|
1010
|
-
const entityElement = this.querySelector(`pc-entity[name="${currentNode.name}"]`);
|
|
1011
|
-
if (entityElement) {
|
|
1012
|
-
newHoverEntity = entityElement;
|
|
1013
|
-
break;
|
|
1014
|
-
}
|
|
1015
|
-
currentNode = currentNode.parent;
|
|
1016
|
-
}
|
|
1197
|
+
// The hovered element is the nearest one up the node's parent chain, listening or not -
|
|
1198
|
+
// dispatch is gated per event type below
|
|
1199
|
+
const newHoverEntity = this._elementFromNode(node);
|
|
1017
1200
|
// Handle enter/leave events
|
|
1018
1201
|
if (this._hoveredEntity !== newHoverEntity) {
|
|
1019
|
-
if (this._hoveredEntity && this._hoveredEntity.
|
|
1202
|
+
if (this._hoveredEntity && this._hoveredEntity._hasListeners('pointerleave')) {
|
|
1020
1203
|
this._hoveredEntity.dispatchEvent(new PointerEvent('pointerleave', event));
|
|
1021
1204
|
}
|
|
1022
|
-
if (newHoverEntity && newHoverEntity.
|
|
1205
|
+
if (newHoverEntity && newHoverEntity._hasListeners('pointerenter')) {
|
|
1023
1206
|
newHoverEntity.dispatchEvent(new PointerEvent('pointerenter', event));
|
|
1024
1207
|
}
|
|
1025
1208
|
}
|
|
1026
1209
|
// Update hover state
|
|
1027
1210
|
this._hoveredEntity = newHoverEntity;
|
|
1028
1211
|
// Handle pointermove event
|
|
1029
|
-
if (newHoverEntity && newHoverEntity.
|
|
1212
|
+
if (newHoverEntity && newHoverEntity._hasListeners('pointermove')) {
|
|
1030
1213
|
newHoverEntity.dispatchEvent(new PointerEvent('pointermove', event));
|
|
1031
1214
|
}
|
|
1032
1215
|
}
|
|
1033
1216
|
async _onPointerDown(event) {
|
|
1034
1217
|
if (!this._picker || !this.app)
|
|
1035
1218
|
return;
|
|
1036
|
-
|
|
1219
|
+
const node = await this._pickNode(event);
|
|
1037
1220
|
if (!this._picker)
|
|
1038
1221
|
return; // the element disconnected while the pick was in flight
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
entityElement.dispatchEvent(new PointerEvent('pointerdown', event));
|
|
1043
|
-
break;
|
|
1044
|
-
}
|
|
1045
|
-
currentNode = currentNode.parent;
|
|
1222
|
+
const entityElement = this._elementWithListener(node, 'pointerdown');
|
|
1223
|
+
if (entityElement) {
|
|
1224
|
+
entityElement.dispatchEvent(new PointerEvent('pointerdown', event));
|
|
1046
1225
|
}
|
|
1047
1226
|
}
|
|
1048
1227
|
async _onPointerUp(event) {
|
|
1049
1228
|
if (!this._picker || !this.app)
|
|
1050
1229
|
return;
|
|
1051
1230
|
const node = await this._pickNode(event);
|
|
1052
|
-
if (!
|
|
1053
|
-
return;
|
|
1054
|
-
const entityElement = this.
|
|
1055
|
-
if (entityElement
|
|
1231
|
+
if (!this._picker)
|
|
1232
|
+
return; // the element disconnected while the pick was in flight
|
|
1233
|
+
const entityElement = this._elementWithListener(node, 'pointerup');
|
|
1234
|
+
if (entityElement) {
|
|
1056
1235
|
entityElement.dispatchEvent(new PointerEvent('pointerup', event));
|
|
1057
1236
|
}
|
|
1058
1237
|
}
|
|
@@ -1060,50 +1239,64 @@
|
|
|
1060
1239
|
if (!this._hasPointerListeners[type] && this._canvas) {
|
|
1061
1240
|
this._hasPointerListeners[type] = true;
|
|
1062
1241
|
// For enter/leave events, we need the move handler
|
|
1063
|
-
const handler =
|
|
1064
|
-
this._pointerHandlers.pointermove
|
|
1065
|
-
this._pointerHandlers[type];
|
|
1242
|
+
const handler = type === 'pointerenter' || type === 'pointerleave'
|
|
1243
|
+
? this._pointerHandlers.pointermove
|
|
1244
|
+
: this._pointerHandlers[type];
|
|
1066
1245
|
if (handler) {
|
|
1067
1246
|
this._canvas.addEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);
|
|
1068
1247
|
}
|
|
1069
1248
|
}
|
|
1070
1249
|
}
|
|
1071
1250
|
_onPointerListenerRemoved(type) {
|
|
1072
|
-
const hasListeners = Array.from(this.querySelectorAll('pc-entity'))
|
|
1073
|
-
.some(entity => entity.hasListeners(type));
|
|
1251
|
+
const hasListeners = Array.from(this.querySelectorAll('pc-entity')).some((entity) => entity._hasListeners(type));
|
|
1074
1252
|
if (!hasListeners && this._canvas) {
|
|
1075
1253
|
this._hasPointerListeners[type] = false;
|
|
1076
|
-
const handler =
|
|
1077
|
-
this._pointerHandlers.pointermove
|
|
1078
|
-
this._pointerHandlers[type];
|
|
1254
|
+
const handler = type === 'pointerenter' || type === 'pointerleave'
|
|
1255
|
+
? this._pointerHandlers.pointermove
|
|
1256
|
+
: this._pointerHandlers[type];
|
|
1079
1257
|
if (handler) {
|
|
1080
1258
|
this._canvas.removeEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);
|
|
1081
1259
|
}
|
|
1082
1260
|
}
|
|
1083
1261
|
}
|
|
1084
1262
|
/**
|
|
1085
|
-
*
|
|
1263
|
+
* Warns that a graphics option was written too late to have any effect. These options are read
|
|
1264
|
+
* once, when the element connects and creates its graphics device, so a later write updates
|
|
1265
|
+
* only the element's own property - silently, without this.
|
|
1266
|
+
*
|
|
1267
|
+
* @param name - The name of the option, as its attribute.
|
|
1268
|
+
*/
|
|
1269
|
+
_warnIfBooted(name) {
|
|
1270
|
+
if (this._optionsLocked) {
|
|
1271
|
+
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.`);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Sets whether the frame buffer has an alpha channel, which is what lets the page show through
|
|
1276
|
+
* wherever the scene has not drawn. Read only when the application boots.
|
|
1086
1277
|
* @param value - The alpha flag.
|
|
1087
1278
|
*/
|
|
1088
1279
|
set alpha(value) {
|
|
1280
|
+
this._warnIfBooted('alpha');
|
|
1089
1281
|
this._alpha = value;
|
|
1090
1282
|
}
|
|
1091
1283
|
/**
|
|
1092
|
-
* Gets the alpha
|
|
1284
|
+
* Gets whether the frame buffer has an alpha channel.
|
|
1093
1285
|
* @returns The alpha flag.
|
|
1094
1286
|
*/
|
|
1095
1287
|
get alpha() {
|
|
1096
1288
|
return this._alpha;
|
|
1097
1289
|
}
|
|
1098
1290
|
/**
|
|
1099
|
-
* Sets the
|
|
1291
|
+
* Sets whether the frame buffer is anti-aliased. Read only when the application boots.
|
|
1100
1292
|
* @param value - The antialias flag.
|
|
1101
1293
|
*/
|
|
1102
1294
|
set antialias(value) {
|
|
1295
|
+
this._warnIfBooted('antialias');
|
|
1103
1296
|
this._antialias = value;
|
|
1104
1297
|
}
|
|
1105
1298
|
/**
|
|
1106
|
-
* Gets the
|
|
1299
|
+
* Gets whether the frame buffer is anti-aliased.
|
|
1107
1300
|
* @returns The antialias flag.
|
|
1108
1301
|
*/
|
|
1109
1302
|
get antialias() {
|
|
@@ -1111,10 +1304,11 @@
|
|
|
1111
1304
|
}
|
|
1112
1305
|
/**
|
|
1113
1306
|
* Sets the graphics backend. Defaults to 'webgpu', which falls back to 'webgl2' if WebGPU
|
|
1114
|
-
* is not supported by the browser.
|
|
1307
|
+
* is not supported by the browser. Read only when the application boots.
|
|
1115
1308
|
* @param value - The graphics backend ('webgpu', 'webgl2', or 'null').
|
|
1116
1309
|
*/
|
|
1117
1310
|
set backend(value) {
|
|
1311
|
+
this._warnIfBooted('backend');
|
|
1118
1312
|
this._backend = value;
|
|
1119
1313
|
}
|
|
1120
1314
|
/**
|
|
@@ -1125,44 +1319,20 @@
|
|
|
1125
1319
|
return this._backend;
|
|
1126
1320
|
}
|
|
1127
1321
|
/**
|
|
1128
|
-
* Sets the depth
|
|
1129
|
-
*
|
|
1130
|
-
|
|
1131
|
-
set depth(value) {
|
|
1132
|
-
this._depth = value;
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* Gets the depth flag.
|
|
1136
|
-
* @returns The depth flag.
|
|
1137
|
-
*/
|
|
1138
|
-
get depth() {
|
|
1139
|
-
return this._depth;
|
|
1140
|
-
}
|
|
1141
|
-
/**
|
|
1142
|
-
* Gets the hierarchy ready flag.
|
|
1143
|
-
* @returns The hierarchy ready flag.
|
|
1144
|
-
* @ignore
|
|
1145
|
-
*/
|
|
1146
|
-
get hierarchyReady() {
|
|
1147
|
-
return this._hierarchyReady;
|
|
1148
|
-
}
|
|
1149
|
-
/**
|
|
1150
|
-
* Sets the high resolution flag. When true, the application will render at the device's
|
|
1151
|
-
* physical resolution. When false, the application will render at CSS resolution.
|
|
1152
|
-
* @param value - The high resolution flag.
|
|
1322
|
+
* Sets whether the frame buffer has a depth buffer, which the renderer needs to resolve which
|
|
1323
|
+
* surface is nearest the camera. Read only when the application boots.
|
|
1324
|
+
* @param value - The depth buffer flag.
|
|
1153
1325
|
*/
|
|
1154
|
-
set
|
|
1155
|
-
this.
|
|
1156
|
-
|
|
1157
|
-
this.app.graphicsDevice.maxPixelRatio = value ? window.devicePixelRatio : 1;
|
|
1158
|
-
}
|
|
1326
|
+
set depthBuffer(value) {
|
|
1327
|
+
this._warnIfBooted('depth-buffer');
|
|
1328
|
+
this._depthBuffer = value;
|
|
1159
1329
|
}
|
|
1160
1330
|
/**
|
|
1161
|
-
* Gets the
|
|
1162
|
-
* @returns The
|
|
1331
|
+
* Gets whether the frame buffer has a depth buffer.
|
|
1332
|
+
* @returns The depth buffer flag.
|
|
1163
1333
|
*/
|
|
1164
|
-
get
|
|
1165
|
-
return this.
|
|
1334
|
+
get depthBuffer() {
|
|
1335
|
+
return this._depthBuffer;
|
|
1166
1336
|
}
|
|
1167
1337
|
/**
|
|
1168
1338
|
* Sets whether the application shows its built-in loading bar while it boots and preloads its
|
|
@@ -1188,21 +1358,45 @@
|
|
|
1188
1358
|
return this._loadingBar;
|
|
1189
1359
|
}
|
|
1190
1360
|
/**
|
|
1191
|
-
* Sets the
|
|
1192
|
-
*
|
|
1361
|
+
* Sets the cap on the pixel ratio the application renders at. The canvas is sized by the
|
|
1362
|
+
* smaller of this value and the display's own device pixel ratio, so the default of `Infinity`
|
|
1363
|
+
* renders at full physical resolution, `1` renders at CSS resolution, and an intermediate
|
|
1364
|
+
* value such as `2` keeps a dense display sharp without paying for every one of its pixels.
|
|
1365
|
+
* Must be greater than 0. Unlike the other graphics options, this applies immediately.
|
|
1366
|
+
* @param value - The maximum pixel ratio.
|
|
1367
|
+
*/
|
|
1368
|
+
set maxPixelRatio(value) {
|
|
1369
|
+
this._maxPixelRatio = value;
|
|
1370
|
+
if (this.app) {
|
|
1371
|
+
this.app.graphicsDevice.maxPixelRatio = value;
|
|
1372
|
+
this.app.resizeCanvas();
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Gets the cap on the pixel ratio the application renders at.
|
|
1377
|
+
* @returns The maximum pixel ratio.
|
|
1378
|
+
*/
|
|
1379
|
+
get maxPixelRatio() {
|
|
1380
|
+
return this._maxPixelRatio;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Sets whether the frame buffer has a stencil buffer, which stencil-based effects and UI
|
|
1384
|
+
* masking need. Read only when the application boots.
|
|
1385
|
+
* @param value - The stencil buffer flag.
|
|
1193
1386
|
*/
|
|
1194
|
-
set
|
|
1195
|
-
this.
|
|
1387
|
+
set stencilBuffer(value) {
|
|
1388
|
+
this._warnIfBooted('stencil-buffer');
|
|
1389
|
+
this._stencilBuffer = value;
|
|
1196
1390
|
}
|
|
1197
1391
|
/**
|
|
1198
|
-
* Gets the stencil
|
|
1199
|
-
* @returns The stencil flag.
|
|
1392
|
+
* Gets whether the frame buffer has a stencil buffer.
|
|
1393
|
+
* @returns The stencil buffer flag.
|
|
1200
1394
|
*/
|
|
1201
|
-
get
|
|
1202
|
-
return this.
|
|
1395
|
+
get stencilBuffer() {
|
|
1396
|
+
return this._stencilBuffer;
|
|
1203
1397
|
}
|
|
1204
1398
|
static get observedAttributes() {
|
|
1205
|
-
return ['alpha', 'antialias', 'backend', 'depth', '
|
|
1399
|
+
return ['alpha', 'antialias', 'backend', 'depth-buffer', 'loading-bar', 'max-pixel-ratio', 'stencil-buffer'];
|
|
1206
1400
|
}
|
|
1207
1401
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
1208
1402
|
switch (name) {
|
|
@@ -1215,17 +1409,17 @@
|
|
|
1215
1409
|
case 'backend':
|
|
1216
1410
|
this.backend = parseEnum(newValue, ['webgpu', 'webgl2', 'null'], 'webgpu', name);
|
|
1217
1411
|
break;
|
|
1218
|
-
case 'depth':
|
|
1219
|
-
this.
|
|
1220
|
-
break;
|
|
1221
|
-
case 'high-resolution':
|
|
1222
|
-
this.highResolution = parseBool(newValue, true);
|
|
1412
|
+
case 'depth-buffer':
|
|
1413
|
+
this.depthBuffer = parseBool(newValue, true);
|
|
1223
1414
|
break;
|
|
1224
1415
|
case 'loading-bar':
|
|
1225
1416
|
this.loadingBar = parseBool(newValue, true);
|
|
1226
1417
|
break;
|
|
1227
|
-
case '
|
|
1228
|
-
this.
|
|
1418
|
+
case 'max-pixel-ratio':
|
|
1419
|
+
this.maxPixelRatio = parseNumber(newValue, Infinity, name);
|
|
1420
|
+
break;
|
|
1421
|
+
case 'stencil-buffer':
|
|
1422
|
+
this.stencilBuffer = parseBool(newValue, true);
|
|
1229
1423
|
break;
|
|
1230
1424
|
}
|
|
1231
1425
|
}
|
|
@@ -1294,6 +1488,11 @@
|
|
|
1294
1488
|
*/
|
|
1295
1489
|
_built = false;
|
|
1296
1490
|
_entity = null;
|
|
1491
|
+
/**
|
|
1492
|
+
* The application element this entity is registered with, cached at creation time so the
|
|
1493
|
+
* entity can be unregistered even once this element has left the DOM.
|
|
1494
|
+
*/
|
|
1495
|
+
_appElement = null;
|
|
1297
1496
|
/**
|
|
1298
1497
|
* The PlayCanvas entity instance. `null` until the element is ready, and again once it has
|
|
1299
1498
|
* been removed from the document — await {@link whenReady} or the element's `ready()`
|
|
@@ -1303,7 +1502,14 @@
|
|
|
1303
1502
|
get entity() {
|
|
1304
1503
|
return this._entity;
|
|
1305
1504
|
}
|
|
1306
|
-
|
|
1505
|
+
/**
|
|
1506
|
+
* Creates the backing entity. Called by the containing `<pc-app>` element during its boot
|
|
1507
|
+
* sweep, and on connection for elements inserted while the application is already running.
|
|
1508
|
+
*
|
|
1509
|
+
* @param app - The application to create the entity in.
|
|
1510
|
+
* @internal
|
|
1511
|
+
*/
|
|
1512
|
+
_createEntity(app) {
|
|
1307
1513
|
// Guard against double creation. When a subtree is inserted at runtime (e.g. cloning a
|
|
1308
1514
|
// `<template>`), an ancestor's connectedCallback eagerly creates descendant entities; the
|
|
1309
1515
|
// descendants' own connectedCallbacks would otherwise create them a second time.
|
|
@@ -1323,8 +1529,41 @@
|
|
|
1323
1529
|
if (this._tags.length > 0) {
|
|
1324
1530
|
entity.tags.add(this._tags);
|
|
1325
1531
|
}
|
|
1532
|
+
// Register with the owning application, which joins engine nodes back to elements by
|
|
1533
|
+
// identity (never by name), and hook the entity's destruction. The engine fires 'destroy'
|
|
1534
|
+
// for every entity in a destroyed subtree, so the element learns of its entity's death no
|
|
1535
|
+
// matter who causes it: this element, an ancestor, the whole application, or a user
|
|
1536
|
+
// script calling entity.destroy().
|
|
1537
|
+
this._appElement = this.closestApp;
|
|
1538
|
+
this._appElement?._registerEntityElement(entity, this);
|
|
1539
|
+
entity.once('destroy', this._onEntityDestroy, this);
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Handles the destruction of the backing entity. Resets the element so a later re-insertion
|
|
1543
|
+
* starts clean: `_built` must be cleared alongside `_entity`, or _buildHierarchy would bail
|
|
1544
|
+
* and a re-created entity would never be parented. Readiness is re-armed for the same
|
|
1545
|
+
* reason — with the entity gone, a resolved ready promise would resume its awaiters against
|
|
1546
|
+
* a null `entity`.
|
|
1547
|
+
*
|
|
1548
|
+
* @param entity - The entity that was destroyed.
|
|
1549
|
+
*/
|
|
1550
|
+
_onEntityDestroy(entity) {
|
|
1551
|
+
this._appElement?._unregisterEntityElement(entity);
|
|
1552
|
+
this._appElement = null;
|
|
1553
|
+
this._entity = null;
|
|
1554
|
+
this._built = false;
|
|
1555
|
+
this._resetReady();
|
|
1326
1556
|
}
|
|
1327
|
-
|
|
1557
|
+
/**
|
|
1558
|
+
* Parents the backing entity: under the entity of the nearest ancestor `<pc-entity>` when
|
|
1559
|
+
* there is one, and under the application root otherwise. Called by the containing `<pc-app>`
|
|
1560
|
+
* element once a sweep has created every entity, so a parent's existence never depends on
|
|
1561
|
+
* document order.
|
|
1562
|
+
*
|
|
1563
|
+
* @param app - The application whose root adopts parentless entities.
|
|
1564
|
+
* @internal
|
|
1565
|
+
*/
|
|
1566
|
+
_buildHierarchy(app) {
|
|
1328
1567
|
if (!this.entity || this._built)
|
|
1329
1568
|
return;
|
|
1330
1569
|
this._built = true;
|
|
@@ -1350,37 +1589,26 @@
|
|
|
1350
1589
|
return;
|
|
1351
1590
|
}
|
|
1352
1591
|
// If app is already running, create entity immediately
|
|
1353
|
-
if (closestApp.
|
|
1592
|
+
if (closestApp._hierarchyReady) {
|
|
1354
1593
|
const app = closestApp.app;
|
|
1355
|
-
this.
|
|
1356
|
-
this.
|
|
1594
|
+
this._createEntity(app);
|
|
1595
|
+
this._buildHierarchy(app);
|
|
1357
1596
|
// Handle any child entities that might exist
|
|
1358
1597
|
const childEntities = this.querySelectorAll('pc-entity');
|
|
1359
1598
|
childEntities.forEach((child) => {
|
|
1360
|
-
child.
|
|
1599
|
+
child._createEntity(app);
|
|
1361
1600
|
});
|
|
1362
1601
|
childEntities.forEach((child) => {
|
|
1363
|
-
child.
|
|
1602
|
+
child._buildHierarchy(app);
|
|
1364
1603
|
});
|
|
1365
1604
|
}
|
|
1366
1605
|
}
|
|
1367
1606
|
disconnectedCallback() {
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
// on re-insertion, so the descendant would get a fresh entity that is never parented.
|
|
1374
|
-
const children = this.querySelectorAll('pc-entity');
|
|
1375
|
-
children.forEach((child) => {
|
|
1376
|
-
child._entity = null;
|
|
1377
|
-
child._built = false;
|
|
1378
|
-
});
|
|
1379
|
-
// Destroy the entity
|
|
1380
|
-
this.entity.destroy();
|
|
1381
|
-
this._entity = null;
|
|
1382
|
-
this._built = false;
|
|
1383
|
-
}
|
|
1607
|
+
// Destroying the entity destroys its whole subtree, and the engine fires 'destroy' for
|
|
1608
|
+
// every entity in it - so _onEntityDestroy resets this element AND every descendant
|
|
1609
|
+
// element before the descendants' own disconnectedCallbacks run. Their entities are null
|
|
1610
|
+
// by then, making this call a no-op for them.
|
|
1611
|
+
this._entity?.destroy();
|
|
1384
1612
|
}
|
|
1385
1613
|
/**
|
|
1386
1614
|
* Sets the enabled state of the entity.
|
|
@@ -1565,14 +1793,23 @@
|
|
|
1565
1793
|
}
|
|
1566
1794
|
removeEventListener(type, listener, options) {
|
|
1567
1795
|
if (this._listeners[type]) {
|
|
1568
|
-
this._listeners[type] = this._listeners[type].filter(l => l !== listener);
|
|
1796
|
+
this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
|
|
1569
1797
|
}
|
|
1570
1798
|
super.removeEventListener(type, listener, options);
|
|
1571
1799
|
if (type.startsWith('pointer')) {
|
|
1572
1800
|
this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
|
|
1573
1801
|
}
|
|
1574
1802
|
}
|
|
1575
|
-
|
|
1803
|
+
/**
|
|
1804
|
+
* Whether the element has a listener for an event type, registered either with
|
|
1805
|
+
* {@link addEventListener} or with the matching inline `onpointer*` attribute. Read by the
|
|
1806
|
+
* containing `<pc-app>` element to gate pointer event synthesis.
|
|
1807
|
+
*
|
|
1808
|
+
* @param type - The event type.
|
|
1809
|
+
* @returns Whether a listener is registered.
|
|
1810
|
+
* @internal
|
|
1811
|
+
*/
|
|
1812
|
+
_hasListeners(type) {
|
|
1576
1813
|
return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
|
|
1577
1814
|
}
|
|
1578
1815
|
}
|
|
@@ -1863,7 +2100,7 @@
|
|
|
1863
2100
|
const app = appElement.app;
|
|
1864
2101
|
if (!app)
|
|
1865
2102
|
return; // pc-app is re-connecting; its own boot will create this asset
|
|
1866
|
-
this.
|
|
2103
|
+
this._createAsset();
|
|
1867
2104
|
if (this.asset) {
|
|
1868
2105
|
app.assets.add(this.asset); // add() auto-loads when preload is true
|
|
1869
2106
|
if (!this.lazy) {
|
|
@@ -1871,13 +2108,15 @@
|
|
|
1871
2108
|
}
|
|
1872
2109
|
}
|
|
1873
2110
|
}
|
|
1874
|
-
// Never ready if
|
|
2111
|
+
// Never ready if _createAsset failed (unsupported asset type)
|
|
1875
2112
|
if (this.asset) {
|
|
1876
2113
|
this._onReady();
|
|
1877
2114
|
}
|
|
1878
2115
|
}
|
|
1879
2116
|
disconnectedCallback() {
|
|
1880
|
-
this.
|
|
2117
|
+
this._destroyAsset();
|
|
2118
|
+
// Re-arm readiness so a re-inserted element announces the asset it creates then
|
|
2119
|
+
this._resetReady();
|
|
1881
2120
|
}
|
|
1882
2121
|
_onAssetLoad() {
|
|
1883
2122
|
this.dispatchEvent(new Event('load'));
|
|
@@ -1887,7 +2126,14 @@
|
|
|
1887
2126
|
message: err instanceof Error ? err.message : String(err)
|
|
1888
2127
|
}));
|
|
1889
2128
|
}
|
|
1890
|
-
|
|
2129
|
+
/**
|
|
2130
|
+
* Creates the asset from the element's attributes. Called by the containing `<pc-app>`
|
|
2131
|
+
* element during its boot sweep, and on connection for elements inserted while the
|
|
2132
|
+
* application is already running.
|
|
2133
|
+
*
|
|
2134
|
+
* @internal
|
|
2135
|
+
*/
|
|
2136
|
+
_createAsset() {
|
|
1891
2137
|
const id = this.getAttribute('id') || '';
|
|
1892
2138
|
const src = this.getAttribute('src') || '';
|
|
1893
2139
|
let type = this.getAttribute('type');
|
|
@@ -1977,7 +2223,7 @@
|
|
|
1977
2223
|
}
|
|
1978
2224
|
return data;
|
|
1979
2225
|
}
|
|
1980
|
-
|
|
2226
|
+
_destroyAsset() {
|
|
1981
2227
|
if (this.asset) {
|
|
1982
2228
|
// A caller that keeps the Asset alive must not dispatch on a removed element
|
|
1983
2229
|
this.asset.off('load', this._onAssetLoad, this);
|
|
@@ -2005,6 +2251,13 @@
|
|
|
2005
2251
|
get lazy() {
|
|
2006
2252
|
return this._lazy;
|
|
2007
2253
|
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Returns the {@link Asset} created by the `<pc-asset>` element with the given `id`, or
|
|
2256
|
+
* `undefined` if there is no such element or its asset has not been created yet.
|
|
2257
|
+
*
|
|
2258
|
+
* @param id - The `id` of the `<pc-asset>` element.
|
|
2259
|
+
* @returns The asset, or `undefined`.
|
|
2260
|
+
*/
|
|
2008
2261
|
static get(id) {
|
|
2009
2262
|
const assetElement = document.querySelector(`pc-asset[id="${id}"]`);
|
|
2010
2263
|
return assetElement?.asset;
|
|
@@ -2030,6 +2283,14 @@
|
|
|
2030
2283
|
_enabled = true;
|
|
2031
2284
|
_component = null;
|
|
2032
2285
|
_appElement = null;
|
|
2286
|
+
/**
|
|
2287
|
+
* Incremented on every connect and disconnect. connectedCallback captures the value on entry
|
|
2288
|
+
* and abandons itself wherever it resumes from an await if the value has moved on — so a
|
|
2289
|
+
* callback whose element was removed cannot act on a torn-down tree, and one whose element
|
|
2290
|
+
* was removed and re-inserted (which runs a callback of its own) cannot add the component a
|
|
2291
|
+
* second time.
|
|
2292
|
+
*/
|
|
2293
|
+
_connectionGeneration = 0;
|
|
2033
2294
|
/**
|
|
2034
2295
|
* Creates a new ComponentElement instance.
|
|
2035
2296
|
*
|
|
@@ -2040,11 +2301,17 @@
|
|
|
2040
2301
|
super();
|
|
2041
2302
|
this._componentName = componentName;
|
|
2042
2303
|
}
|
|
2043
|
-
|
|
2304
|
+
/**
|
|
2305
|
+
* Returns the data the component is created with. Overridden by subclasses to supply the
|
|
2306
|
+
* initial values of their cached properties.
|
|
2307
|
+
*
|
|
2308
|
+
* @returns The initial component data.
|
|
2309
|
+
*/
|
|
2044
2310
|
getInitialComponentData() {
|
|
2045
2311
|
return {};
|
|
2046
2312
|
}
|
|
2047
|
-
async
|
|
2313
|
+
async _addComponent() {
|
|
2314
|
+
const generation = this._connectionGeneration;
|
|
2048
2315
|
const entityElement = this.closestEntity;
|
|
2049
2316
|
if (!entityElement) {
|
|
2050
2317
|
// A component can only exist on an entity, so an element placed outside one is inert.
|
|
@@ -2054,19 +2321,42 @@
|
|
|
2054
2321
|
return;
|
|
2055
2322
|
}
|
|
2056
2323
|
await entityElement.ready();
|
|
2324
|
+
// The element may have been removed, or removed and re-inserted, while the entity became
|
|
2325
|
+
// ready — the component belongs to the connection that owns the current generation.
|
|
2326
|
+
if (generation !== this._connectionGeneration) {
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2057
2329
|
// Add the component to the entity
|
|
2058
2330
|
const data = this.getInitialComponentData();
|
|
2059
2331
|
this._component = entityElement.entity.addComponent(this._componentName, data);
|
|
2060
2332
|
}
|
|
2061
|
-
|
|
2333
|
+
/**
|
|
2334
|
+
* Configures the newly added component. Overridden by subclasses whose setup goes beyond
|
|
2335
|
+
* the initial data — child-element handling, asset resolution and the like.
|
|
2336
|
+
*/
|
|
2337
|
+
initComponent() {
|
|
2338
|
+
// optional hook
|
|
2339
|
+
}
|
|
2062
2340
|
async connectedCallback() {
|
|
2341
|
+
const generation = ++this._connectionGeneration;
|
|
2063
2342
|
this._appElement = this.closestApp ?? null;
|
|
2064
2343
|
await this._appElement?.ready();
|
|
2065
|
-
|
|
2344
|
+
// The element may have been removed, or removed and re-inserted, while the application
|
|
2345
|
+
// became ready. A re-insertion runs a connectedCallback of its own, so a stale resume
|
|
2346
|
+
// must not add the component alongside it.
|
|
2347
|
+
if (generation !== this._connectionGeneration) {
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
await this._addComponent();
|
|
2351
|
+
if (generation !== this._connectionGeneration) {
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2066
2354
|
this.initComponent();
|
|
2067
2355
|
this._onReady();
|
|
2068
2356
|
}
|
|
2069
2357
|
disconnectedCallback() {
|
|
2358
|
+
// Invalidate any connectedCallback still suspended on an await
|
|
2359
|
+
this._connectionGeneration++;
|
|
2070
2360
|
// Remove the component when the element is disconnected. Skip this when the owning
|
|
2071
2361
|
// application has already been destroyed — removing a <pc-app> disconnects it before
|
|
2072
2362
|
// its children, taking the component systems with it.
|
|
@@ -2075,6 +2365,7 @@
|
|
|
2075
2365
|
}
|
|
2076
2366
|
this._component = null;
|
|
2077
2367
|
this._appElement = null;
|
|
2368
|
+
this._resetReady();
|
|
2078
2369
|
}
|
|
2079
2370
|
/**
|
|
2080
2371
|
* The PlayCanvas component instance. `null` until the element is ready, and also for an
|
|
@@ -2527,6 +2818,10 @@
|
|
|
2527
2818
|
}
|
|
2528
2819
|
customElements.define('pc-button', ButtonComponentElement);
|
|
2529
2820
|
|
|
2821
|
+
const projections = new Map([
|
|
2822
|
+
['perspective', playcanvas.PROJECTION_PERSPECTIVE],
|
|
2823
|
+
['orthographic', playcanvas.PROJECTION_ORTHOGRAPHIC]
|
|
2824
|
+
]);
|
|
2530
2825
|
const tonemaps = new Map([
|
|
2531
2826
|
['none', playcanvas.TONEMAP_NONE],
|
|
2532
2827
|
['linear', playcanvas.TONEMAP_LINEAR],
|
|
@@ -2557,7 +2852,7 @@
|
|
|
2557
2852
|
_gamma = 'srgb';
|
|
2558
2853
|
_horizontalFov = false;
|
|
2559
2854
|
_nearClip = 0.1;
|
|
2560
|
-
|
|
2855
|
+
_projection = 'perspective';
|
|
2561
2856
|
_orthoHeight = 10;
|
|
2562
2857
|
_priority = 0;
|
|
2563
2858
|
_rect = new playcanvas.Vec4(0, 0, 1, 1);
|
|
@@ -2581,12 +2876,12 @@
|
|
|
2581
2876
|
gammaCorrection: this._gamma === 'srgb' ? playcanvas.GAMMA_SRGB : playcanvas.GAMMA_NONE,
|
|
2582
2877
|
horizontalFov: this._horizontalFov,
|
|
2583
2878
|
nearClip: this._nearClip,
|
|
2584
|
-
projection: this.
|
|
2879
|
+
projection: projections.get(this._projection) ?? playcanvas.PROJECTION_PERSPECTIVE,
|
|
2585
2880
|
orthoHeight: this._orthoHeight,
|
|
2586
2881
|
priority: this._priority,
|
|
2587
2882
|
rect: this._rect,
|
|
2588
2883
|
scissorRect: this._scissorRect,
|
|
2589
|
-
toneMapping: tonemaps.get(this._tonemap)
|
|
2884
|
+
toneMapping: tonemaps.get(this._tonemap) ?? playcanvas.TONEMAP_NONE
|
|
2590
2885
|
};
|
|
2591
2886
|
}
|
|
2592
2887
|
get xrAvailable() {
|
|
@@ -2828,23 +3123,6 @@
|
|
|
2828
3123
|
get nearClip() {
|
|
2829
3124
|
return this._nearClip;
|
|
2830
3125
|
}
|
|
2831
|
-
/**
|
|
2832
|
-
* Sets the orthographic projection of the camera.
|
|
2833
|
-
* @param value - The orthographic projection.
|
|
2834
|
-
*/
|
|
2835
|
-
set orthographic(value) {
|
|
2836
|
-
this._orthographic = value;
|
|
2837
|
-
if (this.component) {
|
|
2838
|
-
this.component.projection = value ? playcanvas.PROJECTION_ORTHOGRAPHIC : playcanvas.PROJECTION_PERSPECTIVE;
|
|
2839
|
-
}
|
|
2840
|
-
}
|
|
2841
|
-
/**
|
|
2842
|
-
* Gets the orthographic projection of the camera.
|
|
2843
|
-
* @returns The orthographic projection.
|
|
2844
|
-
*/
|
|
2845
|
-
get orthographic() {
|
|
2846
|
-
return this._orthographic;
|
|
2847
|
-
}
|
|
2848
3126
|
/**
|
|
2849
3127
|
* Sets the orthographic height of the camera.
|
|
2850
3128
|
* @param value - The orthographic height.
|
|
@@ -2879,6 +3157,23 @@
|
|
|
2879
3157
|
get priority() {
|
|
2880
3158
|
return this._priority;
|
|
2881
3159
|
}
|
|
3160
|
+
/**
|
|
3161
|
+
* Sets the projection of the camera. Use `orthoHeight` to size an orthographic projection.
|
|
3162
|
+
* @param value - The projection ('perspective' or 'orthographic').
|
|
3163
|
+
*/
|
|
3164
|
+
set projection(value) {
|
|
3165
|
+
this._projection = value;
|
|
3166
|
+
if (this.component) {
|
|
3167
|
+
this.component.projection = projections.get(value) ?? playcanvas.PROJECTION_PERSPECTIVE;
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Gets the projection of the camera.
|
|
3172
|
+
* @returns The projection.
|
|
3173
|
+
*/
|
|
3174
|
+
get projection() {
|
|
3175
|
+
return this._projection;
|
|
3176
|
+
}
|
|
2882
3177
|
/**
|
|
2883
3178
|
* Sets the rect of the camera.
|
|
2884
3179
|
* @param value - The rect.
|
|
@@ -2945,9 +3240,9 @@
|
|
|
2945
3240
|
'gamma',
|
|
2946
3241
|
'horizontal-fov',
|
|
2947
3242
|
'near-clip',
|
|
2948
|
-
'orthographic',
|
|
2949
3243
|
'ortho-height',
|
|
2950
3244
|
'priority',
|
|
3245
|
+
'projection',
|
|
2951
3246
|
'rect',
|
|
2952
3247
|
'scissor-rect',
|
|
2953
3248
|
'tonemap'
|
|
@@ -2992,15 +3287,15 @@
|
|
|
2992
3287
|
case 'near-clip':
|
|
2993
3288
|
this.nearClip = parseNumber(newValue, 0.1, name);
|
|
2994
3289
|
break;
|
|
2995
|
-
case 'orthographic':
|
|
2996
|
-
this.orthographic = parseBool(newValue, false);
|
|
2997
|
-
break;
|
|
2998
3290
|
case 'ortho-height':
|
|
2999
3291
|
this.orthoHeight = parseNumber(newValue, 10, name);
|
|
3000
3292
|
break;
|
|
3001
3293
|
case 'priority':
|
|
3002
3294
|
this.priority = parseNumber(newValue, 0, name);
|
|
3003
3295
|
break;
|
|
3296
|
+
case 'projection':
|
|
3297
|
+
this.projection = parseEnum(newValue, projections, 'perspective', name);
|
|
3298
|
+
break;
|
|
3004
3299
|
case 'rect':
|
|
3005
3300
|
this.rect = parseVec4(newValue, new playcanvas.Vec4(0, 0, 1, 1), name);
|
|
3006
3301
|
break;
|
|
@@ -3128,7 +3423,17 @@
|
|
|
3128
3423
|
return this._type;
|
|
3129
3424
|
}
|
|
3130
3425
|
static get observedAttributes() {
|
|
3131
|
-
return [
|
|
3426
|
+
return [
|
|
3427
|
+
...super.observedAttributes,
|
|
3428
|
+
'angular-offset',
|
|
3429
|
+
'axis',
|
|
3430
|
+
'convex-hull',
|
|
3431
|
+
'half-extents',
|
|
3432
|
+
'height',
|
|
3433
|
+
'linear-offset',
|
|
3434
|
+
'radius',
|
|
3435
|
+
'type'
|
|
3436
|
+
];
|
|
3132
3437
|
}
|
|
3133
3438
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
3134
3439
|
super.attributeChangedCallback(name, _oldValue, newValue);
|
|
@@ -4833,7 +5138,7 @@
|
|
|
4833
5138
|
}
|
|
4834
5139
|
// Set all the config properties on the component
|
|
4835
5140
|
for (const key in resource) {
|
|
4836
|
-
if (
|
|
5141
|
+
if (Object.hasOwn(resource, key)) {
|
|
4837
5142
|
this.component[key] = resource[key];
|
|
4838
5143
|
}
|
|
4839
5144
|
}
|
|
@@ -4906,10 +5211,7 @@
|
|
|
4906
5211
|
}
|
|
4907
5212
|
}
|
|
4908
5213
|
static get observedAttributes() {
|
|
4909
|
-
return [
|
|
4910
|
-
...super.observedAttributes,
|
|
4911
|
-
'asset'
|
|
4912
|
-
];
|
|
5214
|
+
return [...super.observedAttributes, 'asset'];
|
|
4913
5215
|
}
|
|
4914
5216
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
4915
5217
|
super.attributeChangedCallback(name, _oldValue, newValue);
|
|
@@ -5069,7 +5371,7 @@
|
|
|
5069
5371
|
_twoSidedLighting = false;
|
|
5070
5372
|
_useFog = true;
|
|
5071
5373
|
_useLighting = true;
|
|
5072
|
-
// Diverges from the engine default of false - see the class docblock and
|
|
5374
|
+
// Diverges from the engine default of false - see the class docblock and _createMaterial()
|
|
5073
5375
|
_useMetalness = true;
|
|
5074
5376
|
_useMetalnessSpecularColor = false;
|
|
5075
5377
|
_useSkybox = true;
|
|
@@ -5082,6 +5384,10 @@
|
|
|
5082
5384
|
_mapHandles = new Map();
|
|
5083
5385
|
_updateScheduled = false;
|
|
5084
5386
|
_glossConflictWarned = false;
|
|
5387
|
+
/**
|
|
5388
|
+
* The material. `null` until the containing application has created it — an element present
|
|
5389
|
+
* at startup has its material once the application is ready.
|
|
5390
|
+
*/
|
|
5085
5391
|
material = null;
|
|
5086
5392
|
async connectedCallback() {
|
|
5087
5393
|
const appElement = this.parentElement?.closest('pc-app') ?? null;
|
|
@@ -5099,10 +5405,17 @@
|
|
|
5099
5405
|
if (!this.material) {
|
|
5100
5406
|
if (!appElement.app)
|
|
5101
5407
|
return; // pc-app is re-connecting; its own boot will create this
|
|
5102
|
-
this.
|
|
5408
|
+
this._createMaterial();
|
|
5103
5409
|
}
|
|
5104
5410
|
}
|
|
5105
|
-
|
|
5411
|
+
/**
|
|
5412
|
+
* Creates the material from the element's cached properties. Called by the containing
|
|
5413
|
+
* `<pc-app>` element during its boot sweep, and on connection for elements inserted while
|
|
5414
|
+
* the application is already running.
|
|
5415
|
+
*
|
|
5416
|
+
* @internal
|
|
5417
|
+
*/
|
|
5418
|
+
_createMaterial() {
|
|
5106
5419
|
const material = new playcanvas.StandardMaterial();
|
|
5107
5420
|
this.material = material;
|
|
5108
5421
|
material.alphaTest = this._alphaTest;
|
|
@@ -5229,9 +5542,9 @@
|
|
|
5229
5542
|
* warning latches and reports once per episode, clearing when the clash is resolved.
|
|
5230
5543
|
*/
|
|
5231
5544
|
_warnGlossConflict() {
|
|
5232
|
-
const quote = (names) => `'${names.join('
|
|
5233
|
-
const roughness = roughnessAliases.filter(name => this.hasAttribute(name));
|
|
5234
|
-
const gloss = glossConflicts.filter(name => this.hasAttribute(name));
|
|
5545
|
+
const quote = (names) => `'${names.join("', '")}'`;
|
|
5546
|
+
const roughness = roughnessAliases.filter((name) => this.hasAttribute(name));
|
|
5547
|
+
const gloss = glossConflicts.filter((name) => this.hasAttribute(name));
|
|
5235
5548
|
if (roughness.length === 0 || gloss.length === 0) {
|
|
5236
5549
|
this._glossConflictWarned = false;
|
|
5237
5550
|
return;
|
|
@@ -5249,7 +5562,7 @@
|
|
|
5249
5562
|
* @param id - The id of the `pc-asset`, or an empty string to clear the slot.
|
|
5250
5563
|
* @param slot - The material property to write.
|
|
5251
5564
|
*/
|
|
5252
|
-
|
|
5565
|
+
_setMap(id, slot) {
|
|
5253
5566
|
// Drop any load still pending for this slot - its texture is no longer the one we want
|
|
5254
5567
|
this._mapHandles.get(slot)?.off();
|
|
5255
5568
|
this._mapHandles.delete(slot);
|
|
@@ -5343,7 +5656,7 @@
|
|
|
5343
5656
|
*/
|
|
5344
5657
|
set aoMap(value) {
|
|
5345
5658
|
this._aoMap = value;
|
|
5346
|
-
this.
|
|
5659
|
+
this._setMap(value, 'aoMap');
|
|
5347
5660
|
}
|
|
5348
5661
|
/**
|
|
5349
5662
|
* Gets the id of the `pc-asset` used as the ambient occlusion map.
|
|
@@ -5575,7 +5888,7 @@
|
|
|
5575
5888
|
*/
|
|
5576
5889
|
set diffuseMap(value) {
|
|
5577
5890
|
this._diffuseMap = value;
|
|
5578
|
-
this.
|
|
5891
|
+
this._setMap(value, 'diffuseMap');
|
|
5579
5892
|
}
|
|
5580
5893
|
/**
|
|
5581
5894
|
* Gets the id of the `pc-asset` used as the diffuse map.
|
|
@@ -5716,7 +6029,7 @@
|
|
|
5716
6029
|
*/
|
|
5717
6030
|
set emissiveMap(value) {
|
|
5718
6031
|
this._emissiveMap = value;
|
|
5719
|
-
this.
|
|
6032
|
+
this._setMap(value, 'emissiveMap');
|
|
5720
6033
|
}
|
|
5721
6034
|
/**
|
|
5722
6035
|
* Gets the id of the `pc-asset` used as the emissive map.
|
|
@@ -5894,7 +6207,7 @@
|
|
|
5894
6207
|
*/
|
|
5895
6208
|
set glossMap(value) {
|
|
5896
6209
|
this._glossMap = value;
|
|
5897
|
-
this.
|
|
6210
|
+
this._setMap(value, 'glossMap');
|
|
5898
6211
|
}
|
|
5899
6212
|
/**
|
|
5900
6213
|
* Gets the id of the `pc-asset` used as the gloss map.
|
|
@@ -5999,7 +6312,7 @@
|
|
|
5999
6312
|
*/
|
|
6000
6313
|
set heightMap(value) {
|
|
6001
6314
|
this._heightMap = value;
|
|
6002
|
-
this.
|
|
6315
|
+
this._setMap(value, 'heightMap');
|
|
6003
6316
|
}
|
|
6004
6317
|
/**
|
|
6005
6318
|
* Gets the id of the `pc-asset` used as the height map.
|
|
@@ -6140,7 +6453,7 @@
|
|
|
6140
6453
|
*/
|
|
6141
6454
|
set metalnessMap(value) {
|
|
6142
6455
|
this._metalnessMap = value;
|
|
6143
|
-
this.
|
|
6456
|
+
this._setMap(value, 'metalnessMap');
|
|
6144
6457
|
}
|
|
6145
6458
|
/**
|
|
6146
6459
|
* Gets the id of the `pc-asset` used as the metalness map.
|
|
@@ -6245,7 +6558,7 @@
|
|
|
6245
6558
|
*/
|
|
6246
6559
|
set normalMap(value) {
|
|
6247
6560
|
this._normalMap = value;
|
|
6248
|
-
this.
|
|
6561
|
+
this._setMap(value, 'normalMap');
|
|
6249
6562
|
}
|
|
6250
6563
|
/**
|
|
6251
6564
|
* Gets the id of the `pc-asset` used as the normal map.
|
|
@@ -6333,7 +6646,7 @@
|
|
|
6333
6646
|
set occludeDirect(value) {
|
|
6334
6647
|
this._occludeDirect = value;
|
|
6335
6648
|
if (this.material) {
|
|
6336
|
-
// @ts-ignore see
|
|
6649
|
+
// @ts-ignore see _createMaterial() - the engine mistypes occludeDirect as a number
|
|
6337
6650
|
this.material.occludeDirect = value;
|
|
6338
6651
|
this._scheduleUpdate();
|
|
6339
6652
|
}
|
|
@@ -6425,7 +6738,7 @@
|
|
|
6425
6738
|
*/
|
|
6426
6739
|
set opacityMap(value) {
|
|
6427
6740
|
this._opacityMap = value;
|
|
6428
|
-
this.
|
|
6741
|
+
this._setMap(value, 'opacityMap');
|
|
6429
6742
|
}
|
|
6430
6743
|
/**
|
|
6431
6744
|
* Gets the id of the `pc-asset` used as the opacity map.
|
|
@@ -6743,6 +7056,13 @@
|
|
|
6743
7056
|
get useTonemap() {
|
|
6744
7057
|
return this._useTonemap;
|
|
6745
7058
|
}
|
|
7059
|
+
/**
|
|
7060
|
+
* Returns the {@link StandardMaterial} created by the `<pc-material>` element with the given
|
|
7061
|
+
* `id`, or `undefined` if there is no such element or its material has not been created yet.
|
|
7062
|
+
*
|
|
7063
|
+
* @param id - The `id` of the `<pc-material>` element.
|
|
7064
|
+
* @returns The material, or `undefined`.
|
|
7065
|
+
*/
|
|
6746
7066
|
static get(id) {
|
|
6747
7067
|
const materialElement = document.querySelector(`pc-material[id="${id}"]`);
|
|
6748
7068
|
return materialElement?.material;
|
|
@@ -7381,7 +7701,18 @@
|
|
|
7381
7701
|
return this._type;
|
|
7382
7702
|
}
|
|
7383
7703
|
static get observedAttributes() {
|
|
7384
|
-
return [
|
|
7704
|
+
return [
|
|
7705
|
+
...super.observedAttributes,
|
|
7706
|
+
'angular-damping',
|
|
7707
|
+
'angular-factor',
|
|
7708
|
+
'friction',
|
|
7709
|
+
'linear-damping',
|
|
7710
|
+
'linear-factor',
|
|
7711
|
+
'mass',
|
|
7712
|
+
'restitution',
|
|
7713
|
+
'rolling-friction',
|
|
7714
|
+
'type'
|
|
7715
|
+
];
|
|
7385
7716
|
}
|
|
7386
7717
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
7387
7718
|
super.attributeChangedCallback(name, _oldValue, newValue);
|
|
@@ -7418,6 +7749,14 @@
|
|
|
7418
7749
|
}
|
|
7419
7750
|
customElements.define('pc-rigidbody', RigidBodyComponentElement);
|
|
7420
7751
|
|
|
7752
|
+
// The engine's SCALEMODE_* constants are the strings 'none' and 'blend', so this map happens to be
|
|
7753
|
+
// an identity. It is still the right shape: it supplies parseEnum's valid-name list, it is what the
|
|
7754
|
+
// manifest generator reads the enum values from, and it keeps the attribute vocabulary independent
|
|
7755
|
+
// of constants the engine is free to change.
|
|
7756
|
+
const scaleModes = new Map([
|
|
7757
|
+
['none', playcanvas.SCALEMODE_NONE],
|
|
7758
|
+
['blend', playcanvas.SCALEMODE_BLEND]
|
|
7759
|
+
]);
|
|
7421
7760
|
/**
|
|
7422
7761
|
* The ScreenComponentElement interface provides properties and methods for manipulating
|
|
7423
7762
|
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-screen/ | `<pc-screen>`} elements.
|
|
@@ -7431,7 +7770,7 @@
|
|
|
7431
7770
|
_resolution = new playcanvas.Vec2(640, 320);
|
|
7432
7771
|
_referenceResolution = new playcanvas.Vec2(640, 320);
|
|
7433
7772
|
_priority = 0;
|
|
7434
|
-
|
|
7773
|
+
_scaleMode = 'none';
|
|
7435
7774
|
_scaleBlend = 0.5;
|
|
7436
7775
|
/** @ignore */
|
|
7437
7776
|
constructor() {
|
|
@@ -7443,7 +7782,7 @@
|
|
|
7443
7782
|
referenceResolution: this._referenceResolution,
|
|
7444
7783
|
resolution: this._resolution,
|
|
7445
7784
|
scaleBlend: this._scaleBlend,
|
|
7446
|
-
scaleMode: this.
|
|
7785
|
+
scaleMode: scaleModes.get(this._scaleMode) ?? playcanvas.SCALEMODE_NONE,
|
|
7447
7786
|
screenSpace: this._screenSpace
|
|
7448
7787
|
};
|
|
7449
7788
|
}
|
|
@@ -7481,23 +7820,44 @@
|
|
|
7481
7820
|
get resolution() {
|
|
7482
7821
|
return this._resolution;
|
|
7483
7822
|
}
|
|
7823
|
+
/**
|
|
7824
|
+
* Sets how the screen's `resolution` and `referenceResolution` are weighted against each other
|
|
7825
|
+
* when `scaleMode` is `blend`, from 0 (follow the resolution) to 1 (follow the reference
|
|
7826
|
+
* resolution). Ignored while `scaleMode` is `none`.
|
|
7827
|
+
* @param value - The scale blend factor.
|
|
7828
|
+
*/
|
|
7484
7829
|
set scaleBlend(value) {
|
|
7485
7830
|
this._scaleBlend = value;
|
|
7486
7831
|
if (this.component) {
|
|
7487
7832
|
this.component.scaleBlend = this._scaleBlend;
|
|
7488
7833
|
}
|
|
7489
7834
|
}
|
|
7835
|
+
/**
|
|
7836
|
+
* Gets how the screen's resolutions are weighted against each other.
|
|
7837
|
+
* @returns The scale blend factor.
|
|
7838
|
+
*/
|
|
7490
7839
|
get scaleBlend() {
|
|
7491
7840
|
return this._scaleBlend;
|
|
7492
7841
|
}
|
|
7493
|
-
|
|
7494
|
-
|
|
7842
|
+
/**
|
|
7843
|
+
* Sets how the screen scales its contents. `none` renders at `resolution` and ignores
|
|
7844
|
+
* `referenceResolution`; `blend` scales between the two, weighted by `scaleBlend`, which is what
|
|
7845
|
+
* keeps a UI laid out at one resolution usable at another. Requires `screenSpace` - the engine
|
|
7846
|
+
* forces `none` on a world-space screen, which does not support scaling.
|
|
7847
|
+
* @param value - The scale mode ('none' or 'blend').
|
|
7848
|
+
*/
|
|
7849
|
+
set scaleMode(value) {
|
|
7850
|
+
this._scaleMode = value;
|
|
7495
7851
|
if (this.component) {
|
|
7496
|
-
this.component.scaleMode =
|
|
7852
|
+
this.component.scaleMode = scaleModes.get(value) ?? playcanvas.SCALEMODE_NONE;
|
|
7497
7853
|
}
|
|
7498
7854
|
}
|
|
7499
|
-
|
|
7500
|
-
|
|
7855
|
+
/**
|
|
7856
|
+
* Gets how the screen scales its contents.
|
|
7857
|
+
* @returns The scale mode.
|
|
7858
|
+
*/
|
|
7859
|
+
get scaleMode() {
|
|
7860
|
+
return this._scaleMode;
|
|
7501
7861
|
}
|
|
7502
7862
|
set screenSpace(value) {
|
|
7503
7863
|
this._screenSpace = value;
|
|
@@ -7511,12 +7871,12 @@
|
|
|
7511
7871
|
static get observedAttributes() {
|
|
7512
7872
|
return [
|
|
7513
7873
|
...super.observedAttributes,
|
|
7514
|
-
'blend',
|
|
7515
7874
|
'screen-space',
|
|
7516
7875
|
'resolution',
|
|
7517
7876
|
'reference-resolution',
|
|
7518
7877
|
'priority',
|
|
7519
|
-
'scale-blend'
|
|
7878
|
+
'scale-blend',
|
|
7879
|
+
'scale-mode'
|
|
7520
7880
|
];
|
|
7521
7881
|
}
|
|
7522
7882
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
@@ -7534,8 +7894,8 @@
|
|
|
7534
7894
|
case 'scale-blend':
|
|
7535
7895
|
this.scaleBlend = parseNumber(newValue, 0.5, name);
|
|
7536
7896
|
break;
|
|
7537
|
-
case '
|
|
7538
|
-
this.
|
|
7897
|
+
case 'scale-mode':
|
|
7898
|
+
this.scaleMode = parseEnum(newValue, scaleModes, 'none', name);
|
|
7539
7899
|
break;
|
|
7540
7900
|
case 'screen-space':
|
|
7541
7901
|
this.screenSpace = parseBool(newValue, false);
|
|
@@ -7657,13 +8017,7 @@
|
|
|
7657
8017
|
return this._handle;
|
|
7658
8018
|
}
|
|
7659
8019
|
static get observedAttributes() {
|
|
7660
|
-
return [
|
|
7661
|
-
...super.observedAttributes,
|
|
7662
|
-
'orientation',
|
|
7663
|
-
'value',
|
|
7664
|
-
'handle-size',
|
|
7665
|
-
'handle'
|
|
7666
|
-
];
|
|
8020
|
+
return [...super.observedAttributes, 'orientation', 'value', 'handle-size', 'handle'];
|
|
7667
8021
|
}
|
|
7668
8022
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
7669
8023
|
super.attributeChangedCallback(name, _oldValue, newValue);
|
|
@@ -7758,7 +8112,8 @@
|
|
|
7758
8112
|
return super.component;
|
|
7759
8113
|
}
|
|
7760
8114
|
/**
|
|
7761
|
-
* Sets whether horizontal
|
|
8115
|
+
* Sets whether scrolling along the horizontal axis is enabled. This is a toggle, unlike the
|
|
8116
|
+
* `orientation` of a `<pc-scrollbar>`, for which `horizontal` is one of the accepted values.
|
|
7762
8117
|
* @param value - Whether horizontal scrolling is enabled.
|
|
7763
8118
|
*/
|
|
7764
8119
|
set horizontal(value) {
|
|
@@ -7768,14 +8123,15 @@
|
|
|
7768
8123
|
}
|
|
7769
8124
|
}
|
|
7770
8125
|
/**
|
|
7771
|
-
* Gets whether horizontal
|
|
8126
|
+
* Gets whether scrolling along the horizontal axis is enabled.
|
|
7772
8127
|
* @returns Whether horizontal scrolling is enabled.
|
|
7773
8128
|
*/
|
|
7774
8129
|
get horizontal() {
|
|
7775
8130
|
return this._horizontal;
|
|
7776
8131
|
}
|
|
7777
8132
|
/**
|
|
7778
|
-
* Sets whether vertical
|
|
8133
|
+
* Sets whether scrolling along the vertical axis is enabled. This is a toggle, unlike the
|
|
8134
|
+
* `orientation` of a `<pc-scrollbar>`, for which `vertical` is one of the accepted values.
|
|
7779
8135
|
* @param value - Whether vertical scrolling is enabled.
|
|
7780
8136
|
*/
|
|
7781
8137
|
set vertical(value) {
|
|
@@ -7785,7 +8141,7 @@
|
|
|
7785
8141
|
}
|
|
7786
8142
|
}
|
|
7787
8143
|
/**
|
|
7788
|
-
* Gets whether vertical
|
|
8144
|
+
* Gets whether scrolling along the vertical axis is enabled.
|
|
7789
8145
|
* @returns Whether vertical scrolling is enabled.
|
|
7790
8146
|
*/
|
|
7791
8147
|
get vertical() {
|
|
@@ -7887,7 +8243,8 @@
|
|
|
7887
8243
|
set horizontalScrollbarVisibility(value) {
|
|
7888
8244
|
this._horizontalScrollbarVisibility = value;
|
|
7889
8245
|
if (this.component) {
|
|
7890
|
-
this.component.horizontalScrollbarVisibility =
|
|
8246
|
+
this.component.horizontalScrollbarVisibility =
|
|
8247
|
+
visibilities.get(value) ?? playcanvas.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
|
|
7891
8248
|
}
|
|
7892
8249
|
}
|
|
7893
8250
|
/**
|
|
@@ -7905,7 +8262,8 @@
|
|
|
7905
8262
|
set verticalScrollbarVisibility(value) {
|
|
7906
8263
|
this._verticalScrollbarVisibility = value;
|
|
7907
8264
|
if (this.component) {
|
|
7908
|
-
this.component.verticalScrollbarVisibility =
|
|
8265
|
+
this.component.verticalScrollbarVisibility =
|
|
8266
|
+
visibilities.get(value) ?? playcanvas.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
|
|
7909
8267
|
}
|
|
7910
8268
|
}
|
|
7911
8269
|
/**
|
|
@@ -8092,15 +8450,9 @@
|
|
|
8092
8450
|
class ScriptElement extends AsyncElement {
|
|
8093
8451
|
_attributes = {};
|
|
8094
8452
|
_enabled = true;
|
|
8095
|
-
/**
|
|
8096
|
-
* Whether readiness has been signalled. Creation can happen more than once over an
|
|
8097
|
-
* element's life (a runtime `name` change recreates the instance), but `ready` is a
|
|
8098
|
-
* one-shot signal, so only the first successful creation fires it.
|
|
8099
|
-
*/
|
|
8100
|
-
_readySignalled = false;
|
|
8101
8453
|
/**
|
|
8102
8454
|
* The Script instance created for this element by its parent `<pc-scripts>` element.
|
|
8103
|
-
* @
|
|
8455
|
+
* @internal
|
|
8104
8456
|
*/
|
|
8105
8457
|
_script = null;
|
|
8106
8458
|
/**
|
|
@@ -8182,14 +8534,20 @@
|
|
|
8182
8534
|
console.warn(`pc-script '${this.getAttribute('name')}' must be a direct child of pc-scripts - script not created`);
|
|
8183
8535
|
}
|
|
8184
8536
|
}
|
|
8537
|
+
disconnectedCallback() {
|
|
8538
|
+
// Re-arm readiness so a re-inserted element announces the instance created for it then.
|
|
8539
|
+
// `_script` is deliberately NOT cleared here: the parent's mutation observer processes
|
|
8540
|
+
// this removal afterwards and reads it to establish which engine script this element
|
|
8541
|
+
// owned - the parent is what clears it.
|
|
8542
|
+
this._resetReady();
|
|
8543
|
+
}
|
|
8185
8544
|
/**
|
|
8186
8545
|
* Called by the parent `<pc-scripts>` element when the script instance has been created.
|
|
8187
|
-
*
|
|
8546
|
+
* Creation can happen more than once per connection (a runtime `name` change recreates the
|
|
8547
|
+
* instance), but `_onReady` signals readiness at most once per cycle.
|
|
8548
|
+
* @internal
|
|
8188
8549
|
*/
|
|
8189
8550
|
_onScriptCreated() {
|
|
8190
|
-
if (this._readySignalled)
|
|
8191
|
-
return;
|
|
8192
|
-
this._readySignalled = true;
|
|
8193
8551
|
this._onReady();
|
|
8194
8552
|
}
|
|
8195
8553
|
static get observedAttributes() {
|
|
@@ -8234,10 +8592,34 @@
|
|
|
8234
8592
|
*/
|
|
8235
8593
|
const RESERVED_ATTRIBUTES = new Set([
|
|
8236
8594
|
...ScriptElement.observedAttributes,
|
|
8237
|
-
'accesskey',
|
|
8238
|
-
'
|
|
8239
|
-
'
|
|
8240
|
-
'
|
|
8595
|
+
'accesskey',
|
|
8596
|
+
'autocapitalize',
|
|
8597
|
+
'autofocus',
|
|
8598
|
+
'class',
|
|
8599
|
+
'contenteditable',
|
|
8600
|
+
'dir',
|
|
8601
|
+
'draggable',
|
|
8602
|
+
'exportparts',
|
|
8603
|
+
'hidden',
|
|
8604
|
+
'id',
|
|
8605
|
+
'inert',
|
|
8606
|
+
'is',
|
|
8607
|
+
'itemid',
|
|
8608
|
+
'itemprop',
|
|
8609
|
+
'itemref',
|
|
8610
|
+
'itemscope',
|
|
8611
|
+
'itemtype',
|
|
8612
|
+
'lang',
|
|
8613
|
+
'nonce',
|
|
8614
|
+
'part',
|
|
8615
|
+
'popover',
|
|
8616
|
+
'role',
|
|
8617
|
+
'slot',
|
|
8618
|
+
'spellcheck',
|
|
8619
|
+
'style',
|
|
8620
|
+
'tabindex',
|
|
8621
|
+
'title',
|
|
8622
|
+
'translate'
|
|
8241
8623
|
]);
|
|
8242
8624
|
/**
|
|
8243
8625
|
* Checks whether a `pc-script` attribute name is reserved (and so never maps to a script
|
|
@@ -8249,18 +8631,25 @@
|
|
|
8249
8631
|
* @returns Whether the attribute name is reserved.
|
|
8250
8632
|
*/
|
|
8251
8633
|
const isReservedAttribute = (name) => {
|
|
8252
|
-
return RESERVED_ATTRIBUTES.has(name) ||
|
|
8634
|
+
return (RESERVED_ATTRIBUTES.has(name) ||
|
|
8253
8635
|
name.startsWith('data-') ||
|
|
8254
8636
|
name.startsWith('aria-') ||
|
|
8255
8637
|
name.startsWith('_') ||
|
|
8256
|
-
(name.startsWith('on') && name in HTMLElement.prototype);
|
|
8638
|
+
(name.startsWith('on') && name in HTMLElement.prototype));
|
|
8257
8639
|
};
|
|
8258
8640
|
/**
|
|
8259
8641
|
* Script API members that per-property attributes must never overwrite: the engine bindings and
|
|
8260
8642
|
* the (optional, so possibly undefined) lifecycle methods.
|
|
8261
8643
|
*/
|
|
8262
8644
|
const SCRIPT_API_MEMBERS = new Set([
|
|
8263
|
-
'app',
|
|
8645
|
+
'app',
|
|
8646
|
+
'entity',
|
|
8647
|
+
'destroy',
|
|
8648
|
+
'initialize',
|
|
8649
|
+
'postInitialize',
|
|
8650
|
+
'postUpdate',
|
|
8651
|
+
'swap',
|
|
8652
|
+
'update'
|
|
8264
8653
|
]);
|
|
8265
8654
|
/**
|
|
8266
8655
|
* Converts a kebab-case attribute name to the camelCase script attribute name.
|
|
@@ -8276,7 +8665,7 @@
|
|
|
8276
8665
|
* @returns The kebab-case name.
|
|
8277
8666
|
*/
|
|
8278
8667
|
const camelToKebab = (name) => {
|
|
8279
|
-
return name.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`);
|
|
8668
|
+
return name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
|
|
8280
8669
|
};
|
|
8281
8670
|
/**
|
|
8282
8671
|
* Resolves an `asset:` prefix to the Asset created by the `pc-asset` element with that id.
|
|
@@ -8475,7 +8864,10 @@
|
|
|
8475
8864
|
// Only recurse into plain objects. Class instances (Vec3, Color, Asset, Entity...)
|
|
8476
8865
|
// are leaf values assigned whole, so accessor-typed script attributes receive them
|
|
8477
8866
|
// through their setters instead of having a getter's returned copy mutated.
|
|
8478
|
-
if (value &&
|
|
8867
|
+
if (value &&
|
|
8868
|
+
typeof value === 'object' &&
|
|
8869
|
+
!Array.isArray(value) &&
|
|
8870
|
+
Object.getPrototypeOf(value) === Object.prototype) {
|
|
8479
8871
|
if (!current || typeof current !== 'object') {
|
|
8480
8872
|
target[key] = {};
|
|
8481
8873
|
}
|
|
@@ -8493,7 +8885,11 @@
|
|
|
8493
8885
|
* @returns Whether the value is a math type.
|
|
8494
8886
|
*/
|
|
8495
8887
|
isMathType(value) {
|
|
8496
|
-
return value instanceof playcanvas.Vec2 ||
|
|
8888
|
+
return (value instanceof playcanvas.Vec2 ||
|
|
8889
|
+
value instanceof playcanvas.Vec3 ||
|
|
8890
|
+
value instanceof playcanvas.Vec4 ||
|
|
8891
|
+
value instanceof playcanvas.Color ||
|
|
8892
|
+
value instanceof playcanvas.Quat);
|
|
8497
8893
|
}
|
|
8498
8894
|
/**
|
|
8499
8895
|
* Converts a plain numeric array to the math type of `current`. A 3-element array targeting
|
|
@@ -8506,7 +8902,7 @@
|
|
|
8506
8902
|
* @returns The converted value, or `null`.
|
|
8507
8903
|
*/
|
|
8508
8904
|
arrayToMathType(current, value, key) {
|
|
8509
|
-
if (value.every(component => typeof component === 'number' && Number.isFinite(component))) {
|
|
8905
|
+
if (value.every((component) => typeof component === 'number' && Number.isFinite(component))) {
|
|
8510
8906
|
if (current instanceof playcanvas.Vec2 && value.length === 2)
|
|
8511
8907
|
return new playcanvas.Vec2(value);
|
|
8512
8908
|
if (current instanceof playcanvas.Vec3 && value.length === 3)
|
|
@@ -8778,7 +9174,10 @@
|
|
|
8778
9174
|
mutation.removedNodes.forEach((node) => {
|
|
8779
9175
|
if (node instanceof ScriptElement) {
|
|
8780
9176
|
const scriptName = node.getAttribute('name');
|
|
8781
|
-
if (scriptName &&
|
|
9177
|
+
if (scriptName &&
|
|
9178
|
+
node._script &&
|
|
9179
|
+
this.component &&
|
|
9180
|
+
this.component.get(scriptName) === node._script) {
|
|
8782
9181
|
this.destroyScript(scriptName);
|
|
8783
9182
|
}
|
|
8784
9183
|
node._script = null;
|
|
@@ -9027,18 +9426,27 @@
|
|
|
9027
9426
|
* emit a misleading "must be a direct child" warning for what is an ordinary removal.
|
|
9028
9427
|
*/
|
|
9029
9428
|
_soundElement = null;
|
|
9429
|
+
/**
|
|
9430
|
+
* Incremented on every connect and disconnect, and captured by connectedCallback on entry —
|
|
9431
|
+
* a resume from an await abandons itself if the value has moved on, so a stale callback can
|
|
9432
|
+
* neither act on a torn-down tree nor add its slot alongside a re-inserted element's own
|
|
9433
|
+
* callback.
|
|
9434
|
+
*/
|
|
9435
|
+
_connectionGeneration = 0;
|
|
9030
9436
|
/**
|
|
9031
9437
|
* The sound slot.
|
|
9032
9438
|
*/
|
|
9033
9439
|
soundSlot = null;
|
|
9034
9440
|
async connectedCallback() {
|
|
9441
|
+
const generation = ++this._connectionGeneration;
|
|
9035
9442
|
const soundElement = this.soundElement;
|
|
9036
9443
|
await soundElement?.ready();
|
|
9037
|
-
// The element may have been removed
|
|
9038
|
-
//
|
|
9039
|
-
// already be gone - see the
|
|
9444
|
+
// The element may have been removed (perhaps re-inserted, which runs a callback of its
|
|
9445
|
+
// own), or its parent torn down, while we were waiting. A <pc-app> disconnects before
|
|
9446
|
+
// its children, so by the time we resume the component can already be gone - see the
|
|
9447
|
+
// matching guard in disconnectedCallback below.
|
|
9040
9448
|
const component = soundElement?.component;
|
|
9041
|
-
if (
|
|
9449
|
+
if (generation !== this._connectionGeneration || !component) {
|
|
9042
9450
|
return;
|
|
9043
9451
|
}
|
|
9044
9452
|
const options = {
|
|
@@ -9061,12 +9469,15 @@
|
|
|
9061
9469
|
this._onReady();
|
|
9062
9470
|
}
|
|
9063
9471
|
disconnectedCallback() {
|
|
9472
|
+
// Invalidate any connectedCallback still suspended on an await
|
|
9473
|
+
this._connectionGeneration++;
|
|
9064
9474
|
// Uses the cached parent rather than a fresh lookup, since parentElement is already null
|
|
9065
9475
|
// by now. The component itself is null if the parent <pc-sound> (or the whole <pc-app>) is
|
|
9066
9476
|
// being torn down — parents disconnect first and have already removed the component.
|
|
9067
9477
|
this._soundElement?.component?.removeSlot(this._name);
|
|
9068
9478
|
this._soundElement = null;
|
|
9069
9479
|
this.soundSlot = null;
|
|
9480
|
+
this._resetReady();
|
|
9070
9481
|
}
|
|
9071
9482
|
get soundElement() {
|
|
9072
9483
|
const soundElement = this.parentElement;
|
|
@@ -9466,39 +9877,85 @@
|
|
|
9466
9877
|
class ModelElement extends AsyncElement {
|
|
9467
9878
|
_asset = '';
|
|
9468
9879
|
_entity = null;
|
|
9880
|
+
/**
|
|
9881
|
+
* Incremented on every new load and on disconnect, and captured by a load when it starts. A
|
|
9882
|
+
* load that resumes from an await or a load callback abandons itself if the value has moved
|
|
9883
|
+
* on, so a superseded load can neither instantiate a second entity nor parent one that has
|
|
9884
|
+
* since been destroyed.
|
|
9885
|
+
*/
|
|
9886
|
+
_loadGeneration = 0;
|
|
9887
|
+
/**
|
|
9888
|
+
* The pending asset-load subscription of the current load, if it is waiting for its asset.
|
|
9889
|
+
* Held so that whatever supersedes the load can detach the handler from the asset, rather
|
|
9890
|
+
* than leave it registered until the asset loads (or forever, if it never does).
|
|
9891
|
+
*/
|
|
9892
|
+
_loadHandle = null;
|
|
9893
|
+
/**
|
|
9894
|
+
* The root entity of the instantiated model. `null` until the container asset has loaded
|
|
9895
|
+
* and been instantiated, and again once the element has been removed from the document.
|
|
9896
|
+
* @returns The model's root entity, or `null`.
|
|
9897
|
+
*/
|
|
9898
|
+
get entity() {
|
|
9899
|
+
return this._entity;
|
|
9900
|
+
}
|
|
9469
9901
|
connectedCallback() {
|
|
9470
9902
|
this._loadModel();
|
|
9471
9903
|
this._onReady();
|
|
9472
9904
|
}
|
|
9473
9905
|
disconnectedCallback() {
|
|
9906
|
+
this._loadGeneration++;
|
|
9907
|
+
this._detachLoadHandler();
|
|
9474
9908
|
this._unloadModel();
|
|
9909
|
+
this._resetReady();
|
|
9910
|
+
}
|
|
9911
|
+
_detachLoadHandler() {
|
|
9912
|
+
this._loadHandle?.off();
|
|
9913
|
+
this._loadHandle = null;
|
|
9475
9914
|
}
|
|
9476
9915
|
_instantiate(container) {
|
|
9477
|
-
|
|
9916
|
+
const generation = this._loadGeneration;
|
|
9917
|
+
const entity = container.instantiateRenderEntity();
|
|
9918
|
+
this._entity = entity;
|
|
9478
9919
|
// @ts-ignore
|
|
9479
9920
|
if (container.animations.length > 0) {
|
|
9480
|
-
|
|
9921
|
+
entity.addComponent('anim');
|
|
9481
9922
|
// @ts-ignore
|
|
9482
|
-
|
|
9923
|
+
entity.anim.assignAnimation('animation', container.animations[0].resource);
|
|
9483
9924
|
}
|
|
9925
|
+
// The parent's readiness re-arms when it is torn down, so these can resume in a later
|
|
9926
|
+
// connection cycle. The entity is captured above and the generation re-checked, so a
|
|
9927
|
+
// stale resume cannot parent an entity a newer cycle has already destroyed.
|
|
9484
9928
|
const parentEntityElement = this.closestEntity;
|
|
9485
9929
|
if (parentEntityElement) {
|
|
9486
9930
|
parentEntityElement.ready().then(() => {
|
|
9487
|
-
|
|
9931
|
+
if (generation !== this._loadGeneration) {
|
|
9932
|
+
return;
|
|
9933
|
+
}
|
|
9934
|
+
parentEntityElement.entity.addChild(entity);
|
|
9488
9935
|
});
|
|
9489
9936
|
}
|
|
9490
9937
|
else {
|
|
9491
9938
|
const appElement = this.closestApp;
|
|
9492
9939
|
if (appElement) {
|
|
9493
9940
|
appElement.ready().then(() => {
|
|
9494
|
-
|
|
9941
|
+
if (generation !== this._loadGeneration) {
|
|
9942
|
+
return;
|
|
9943
|
+
}
|
|
9944
|
+
appElement.app.root.addChild(entity);
|
|
9495
9945
|
});
|
|
9496
9946
|
}
|
|
9497
9947
|
}
|
|
9498
9948
|
}
|
|
9499
9949
|
async _loadModel() {
|
|
9500
9950
|
this._unloadModel();
|
|
9951
|
+
// Supersede any load already in flight - only the newest load may instantiate
|
|
9952
|
+
const generation = ++this._loadGeneration;
|
|
9953
|
+
this._detachLoadHandler();
|
|
9501
9954
|
const appElement = await this.closestApp?.ready();
|
|
9955
|
+
// The element may have been removed, or another load started, while we waited
|
|
9956
|
+
if (generation !== this._loadGeneration) {
|
|
9957
|
+
return;
|
|
9958
|
+
}
|
|
9502
9959
|
const app = appElement?.app;
|
|
9503
9960
|
const asset = AssetElement.get(this._asset);
|
|
9504
9961
|
if (!asset) {
|
|
@@ -9508,7 +9965,14 @@
|
|
|
9508
9965
|
this._instantiate(asset.resource);
|
|
9509
9966
|
}
|
|
9510
9967
|
else {
|
|
9511
|
-
|
|
9968
|
+
// The generation is re-checked even though a superseded handler is detached: the
|
|
9969
|
+
// detach relies on how the engine's event emitter treats removal, while the check
|
|
9970
|
+
// holds on its own.
|
|
9971
|
+
this._loadHandle = asset.once('load', () => {
|
|
9972
|
+
this._loadHandle = null;
|
|
9973
|
+
if (generation !== this._loadGeneration) {
|
|
9974
|
+
return;
|
|
9975
|
+
}
|
|
9512
9976
|
this._instantiate(asset.resource);
|
|
9513
9977
|
});
|
|
9514
9978
|
app.assets.load(asset);
|
|
@@ -9610,10 +10074,17 @@
|
|
|
9610
10074
|
return;
|
|
9611
10075
|
}
|
|
9612
10076
|
this._scene = app.scene;
|
|
9613
|
-
this.
|
|
10077
|
+
this._updateSceneSettings();
|
|
9614
10078
|
this._onReady();
|
|
9615
10079
|
}
|
|
9616
|
-
|
|
10080
|
+
disconnectedCallback() {
|
|
10081
|
+
// The scene belongs to the application, and removing this element - or the <pc-app>
|
|
10082
|
+
// above it, which disconnects first - parts the two. Re-arm readiness so a re-inserted
|
|
10083
|
+
// element announces the scene it acquires then, not the one it lost here.
|
|
10084
|
+
this._scene = null;
|
|
10085
|
+
this._resetReady();
|
|
10086
|
+
}
|
|
10087
|
+
_updateSceneSettings() {
|
|
9617
10088
|
if (this._scene) {
|
|
9618
10089
|
this._scene.fog.type = this._fog;
|
|
9619
10090
|
this._scene.fog.color = this._fogColor;
|
|
@@ -9775,19 +10246,38 @@
|
|
|
9775
10246
|
_center = new playcanvas.Vec3(0, 0.01, 0);
|
|
9776
10247
|
_intensity = 1;
|
|
9777
10248
|
_rotation = new playcanvas.Vec3();
|
|
9778
|
-
|
|
10249
|
+
_mipLevel = 0;
|
|
9779
10250
|
_lighting = false;
|
|
9780
10251
|
_scale = new playcanvas.Vec3(100, 100, 100);
|
|
9781
10252
|
_type = 'infinite';
|
|
9782
10253
|
_scene = null;
|
|
9783
10254
|
_appElement = null;
|
|
10255
|
+
/**
|
|
10256
|
+
* Incremented on every new load and on disconnect, and captured by a load when it starts. A
|
|
10257
|
+
* load that resumes from an await or a load callback abandons itself if the value has moved
|
|
10258
|
+
* on, so a superseded load cannot generate a skybox for a scene it no longer configures.
|
|
10259
|
+
*/
|
|
10260
|
+
_loadGeneration = 0;
|
|
10261
|
+
/**
|
|
10262
|
+
* The pending asset-load subscription of the current load, if it is waiting for its asset.
|
|
10263
|
+
* Held so that whatever supersedes the load can detach the handler from the asset, rather
|
|
10264
|
+
* than leave it registered until the asset loads (or forever, if it never does).
|
|
10265
|
+
*/
|
|
10266
|
+
_loadHandle = null;
|
|
9784
10267
|
connectedCallback() {
|
|
9785
10268
|
this._loadSkybox();
|
|
9786
10269
|
this._onReady();
|
|
9787
10270
|
}
|
|
9788
10271
|
disconnectedCallback() {
|
|
10272
|
+
this._loadGeneration++;
|
|
10273
|
+
this._detachLoadHandler();
|
|
9789
10274
|
this._unloadSkybox();
|
|
9790
10275
|
this._appElement = null;
|
|
10276
|
+
this._resetReady();
|
|
10277
|
+
}
|
|
10278
|
+
_detachLoadHandler() {
|
|
10279
|
+
this._loadHandle?.off();
|
|
10280
|
+
this._loadHandle = null;
|
|
9791
10281
|
}
|
|
9792
10282
|
_generateSkybox(asset) {
|
|
9793
10283
|
if (!this._scene)
|
|
@@ -9795,10 +10285,17 @@
|
|
|
9795
10285
|
const source = asset.resource;
|
|
9796
10286
|
const skybox = playcanvas.EnvLighting.generateSkyboxCubemap(source);
|
|
9797
10287
|
skybox.anisotropy = 4;
|
|
10288
|
+
// This element owns what it generated (see _unloadSkybox) - replacing a skybox from an
|
|
10289
|
+
// earlier load must release it, not orphan it on the GPU
|
|
10290
|
+
this._scene.skybox?.destroy();
|
|
9798
10291
|
this._scene.skybox = skybox;
|
|
9799
10292
|
if (this._lighting) {
|
|
9800
10293
|
const lighting = playcanvas.EnvLighting.generateLightingSource(source);
|
|
9801
10294
|
const envAtlas = playcanvas.EnvLighting.generateAtlas(lighting);
|
|
10295
|
+
// The lighting source is an intermediate: the atlas is rendered from it and it is
|
|
10296
|
+
// not needed afterwards
|
|
10297
|
+
lighting.destroy();
|
|
10298
|
+
this._scene.envAtlas?.destroy();
|
|
9802
10299
|
this._scene.envAtlas = envAtlas;
|
|
9803
10300
|
}
|
|
9804
10301
|
const layer = this._scene.layers.getLayerById(playcanvas.LAYERID_SKYBOX);
|
|
@@ -9809,10 +10306,17 @@
|
|
|
9809
10306
|
this._scene.sky.node.setLocalScale(this._scale);
|
|
9810
10307
|
this._scene.sky.center = this._center;
|
|
9811
10308
|
this._scene.skyboxIntensity = this._intensity;
|
|
9812
|
-
this._scene.skyboxMip = this.
|
|
10309
|
+
this._scene.skyboxMip = this._mipLevel;
|
|
9813
10310
|
}
|
|
9814
10311
|
async _loadSkybox() {
|
|
10312
|
+
// Supersede any load already in flight - only the newest load may generate the skybox
|
|
10313
|
+
const generation = ++this._loadGeneration;
|
|
10314
|
+
this._detachLoadHandler();
|
|
9815
10315
|
const appElement = await this.closestApp?.ready();
|
|
10316
|
+
// The element may have been removed, or another load started, while we waited
|
|
10317
|
+
if (generation !== this._loadGeneration) {
|
|
10318
|
+
return;
|
|
10319
|
+
}
|
|
9816
10320
|
const app = appElement?.app;
|
|
9817
10321
|
if (!appElement || !app) {
|
|
9818
10322
|
return;
|
|
@@ -9827,7 +10331,14 @@
|
|
|
9827
10331
|
this._generateSkybox(asset);
|
|
9828
10332
|
}
|
|
9829
10333
|
else {
|
|
9830
|
-
|
|
10334
|
+
// The generation is re-checked even though a superseded handler is detached: the
|
|
10335
|
+
// detach relies on how the engine's event emitter treats removal, while the check
|
|
10336
|
+
// holds on its own.
|
|
10337
|
+
this._loadHandle = asset.once('load', () => {
|
|
10338
|
+
this._loadHandle = null;
|
|
10339
|
+
if (generation !== this._loadGeneration) {
|
|
10340
|
+
return;
|
|
10341
|
+
}
|
|
9831
10342
|
this._generateSkybox(asset);
|
|
9832
10343
|
});
|
|
9833
10344
|
app.assets.load(asset);
|
|
@@ -9901,23 +10412,6 @@
|
|
|
9901
10412
|
get intensity() {
|
|
9902
10413
|
return this._intensity;
|
|
9903
10414
|
}
|
|
9904
|
-
/**
|
|
9905
|
-
* Sets the mip level of the skybox.
|
|
9906
|
-
* @param value - The mip level.
|
|
9907
|
-
*/
|
|
9908
|
-
set level(value) {
|
|
9909
|
-
this._level = value;
|
|
9910
|
-
if (this._scene) {
|
|
9911
|
-
this._scene.skyboxMip = this._level;
|
|
9912
|
-
}
|
|
9913
|
-
}
|
|
9914
|
-
/**
|
|
9915
|
-
* Gets the mip level of the skybox.
|
|
9916
|
-
* @returns The mip level.
|
|
9917
|
-
*/
|
|
9918
|
-
get level() {
|
|
9919
|
-
return this._level;
|
|
9920
|
-
}
|
|
9921
10415
|
/**
|
|
9922
10416
|
* Sets whether the skybox is used as a light source.
|
|
9923
10417
|
* @param value - Whether to use lighting.
|
|
@@ -9932,6 +10426,24 @@
|
|
|
9932
10426
|
get lighting() {
|
|
9933
10427
|
return this._lighting;
|
|
9934
10428
|
}
|
|
10429
|
+
/**
|
|
10430
|
+
* Sets the mip level of the skybox, where 0 is the sharpest. Raising it selects a blurrier mip,
|
|
10431
|
+
* which is how a skybox is softened without blurring the texture itself.
|
|
10432
|
+
* @param value - The mip level.
|
|
10433
|
+
*/
|
|
10434
|
+
set mipLevel(value) {
|
|
10435
|
+
this._mipLevel = value;
|
|
10436
|
+
if (this._scene) {
|
|
10437
|
+
this._scene.skyboxMip = this._mipLevel;
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
/**
|
|
10441
|
+
* Gets the mip level of the skybox.
|
|
10442
|
+
* @returns The mip level.
|
|
10443
|
+
*/
|
|
10444
|
+
get mipLevel() {
|
|
10445
|
+
return this._mipLevel;
|
|
10446
|
+
}
|
|
9935
10447
|
/**
|
|
9936
10448
|
* Sets the Euler rotation of the skybox.
|
|
9937
10449
|
* @param value - The rotation.
|
|
@@ -9988,7 +10500,7 @@
|
|
|
9988
10500
|
return this._type;
|
|
9989
10501
|
}
|
|
9990
10502
|
static get observedAttributes() {
|
|
9991
|
-
return ['asset', 'center', 'intensity', '
|
|
10503
|
+
return ['asset', 'center', 'intensity', 'lighting', 'mip-level', 'rotation', 'scale', 'type'];
|
|
9992
10504
|
}
|
|
9993
10505
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
9994
10506
|
switch (name) {
|
|
@@ -10001,12 +10513,12 @@
|
|
|
10001
10513
|
case 'intensity':
|
|
10002
10514
|
this.intensity = parseNumber(newValue, 1, name);
|
|
10003
10515
|
break;
|
|
10004
|
-
case 'level':
|
|
10005
|
-
this.level = parseNumber(newValue, 0, name);
|
|
10006
|
-
break;
|
|
10007
10516
|
case 'lighting':
|
|
10008
10517
|
this.lighting = parseBool(newValue, false);
|
|
10009
10518
|
break;
|
|
10519
|
+
case 'mip-level':
|
|
10520
|
+
this.mipLevel = parseNumber(newValue, 0, name);
|
|
10521
|
+
break;
|
|
10010
10522
|
case 'rotation':
|
|
10011
10523
|
this.rotation = parseVec3(newValue, playcanvas.Vec3.ZERO, name);
|
|
10012
10524
|
break;
|