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