@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/src/model.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContainerResource, Entity } from 'playcanvas';
|
|
1
|
+
import type { ContainerResource, Entity, EventHandle } from 'playcanvas';
|
|
2
2
|
|
|
3
3
|
import { AssetElement } from './asset';
|
|
4
4
|
import { AsyncElement } from './async-element';
|
|
@@ -10,39 +10,83 @@ import { AsyncElement } from './async-element';
|
|
|
10
10
|
* {@link HTMLElement} interface.
|
|
11
11
|
*/
|
|
12
12
|
class ModelElement extends AsyncElement {
|
|
13
|
-
private _asset
|
|
13
|
+
private _asset = '';
|
|
14
14
|
|
|
15
15
|
private _entity: Entity | null = null;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Incremented on every new load and on disconnect, and captured by a load when it starts. A
|
|
19
|
+
* load that resumes from an await or a load callback abandons itself if the value has moved
|
|
20
|
+
* on, so a superseded load can neither instantiate a second entity nor parent one that has
|
|
21
|
+
* since been destroyed.
|
|
22
|
+
*/
|
|
23
|
+
private _loadGeneration = 0;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The pending asset-load subscription of the current load, if it is waiting for its asset.
|
|
27
|
+
* Held so that whatever supersedes the load can detach the handler from the asset, rather
|
|
28
|
+
* than leave it registered until the asset loads (or forever, if it never does).
|
|
29
|
+
*/
|
|
30
|
+
private _loadHandle: EventHandle | null = null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The root entity of the instantiated model. `null` until the container asset has loaded
|
|
34
|
+
* and been instantiated, and again once the element has been removed from the document.
|
|
35
|
+
* @returns The model's root entity, or `null`.
|
|
36
|
+
*/
|
|
37
|
+
get entity(): Entity | null {
|
|
38
|
+
return this._entity;
|
|
39
|
+
}
|
|
40
|
+
|
|
17
41
|
connectedCallback() {
|
|
18
42
|
this._loadModel();
|
|
19
43
|
this._onReady();
|
|
20
44
|
}
|
|
21
45
|
|
|
22
46
|
disconnectedCallback() {
|
|
47
|
+
this._loadGeneration++;
|
|
48
|
+
this._detachLoadHandler();
|
|
23
49
|
this._unloadModel();
|
|
50
|
+
this._resetReady();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private _detachLoadHandler() {
|
|
54
|
+
this._loadHandle?.off();
|
|
55
|
+
this._loadHandle = null;
|
|
24
56
|
}
|
|
25
57
|
|
|
26
58
|
private _instantiate(container: ContainerResource) {
|
|
27
|
-
|
|
59
|
+
const generation = this._loadGeneration;
|
|
60
|
+
|
|
61
|
+
const entity = container.instantiateRenderEntity();
|
|
62
|
+
this._entity = entity;
|
|
28
63
|
|
|
29
64
|
// @ts-ignore
|
|
30
65
|
if (container.animations.length > 0) {
|
|
31
|
-
|
|
66
|
+
entity.addComponent('anim');
|
|
32
67
|
// @ts-ignore
|
|
33
|
-
|
|
68
|
+
entity.anim.assignAnimation('animation', container.animations[0].resource);
|
|
34
69
|
}
|
|
35
70
|
|
|
71
|
+
// The parent's readiness re-arms when it is torn down, so these can resume in a later
|
|
72
|
+
// connection cycle. The entity is captured above and the generation re-checked, so a
|
|
73
|
+
// stale resume cannot parent an entity a newer cycle has already destroyed.
|
|
36
74
|
const parentEntityElement = this.closestEntity;
|
|
37
75
|
if (parentEntityElement) {
|
|
38
76
|
parentEntityElement.ready().then(() => {
|
|
39
|
-
|
|
77
|
+
if (generation !== this._loadGeneration) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
parentEntityElement.entity!.addChild(entity);
|
|
40
81
|
});
|
|
41
82
|
} else {
|
|
42
83
|
const appElement = this.closestApp;
|
|
43
84
|
if (appElement) {
|
|
44
85
|
appElement.ready().then(() => {
|
|
45
|
-
|
|
86
|
+
if (generation !== this._loadGeneration) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
appElement.app!.root.addChild(entity);
|
|
46
90
|
});
|
|
47
91
|
}
|
|
48
92
|
}
|
|
@@ -51,7 +95,17 @@ class ModelElement extends AsyncElement {
|
|
|
51
95
|
private async _loadModel() {
|
|
52
96
|
this._unloadModel();
|
|
53
97
|
|
|
98
|
+
// Supersede any load already in flight - only the newest load may instantiate
|
|
99
|
+
const generation = ++this._loadGeneration;
|
|
100
|
+
this._detachLoadHandler();
|
|
101
|
+
|
|
54
102
|
const appElement = await this.closestApp?.ready();
|
|
103
|
+
|
|
104
|
+
// The element may have been removed, or another load started, while we waited
|
|
105
|
+
if (generation !== this._loadGeneration) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
55
109
|
const app = appElement?.app;
|
|
56
110
|
|
|
57
111
|
const asset = AssetElement.get(this._asset);
|
|
@@ -62,7 +116,14 @@ class ModelElement extends AsyncElement {
|
|
|
62
116
|
if (asset.loaded) {
|
|
63
117
|
this._instantiate(asset.resource as ContainerResource);
|
|
64
118
|
} else {
|
|
65
|
-
|
|
119
|
+
// The generation is re-checked even though a superseded handler is detached: the
|
|
120
|
+
// detach relies on how the engine's event emitter treats removal, while the check
|
|
121
|
+
// holds on its own.
|
|
122
|
+
this._loadHandle = asset.once('load', () => {
|
|
123
|
+
this._loadHandle = null;
|
|
124
|
+
if (generation !== this._loadGeneration) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
66
127
|
this._instantiate(asset.resource as ContainerResource);
|
|
67
128
|
});
|
|
68
129
|
app!.assets.load(asset);
|
|
@@ -108,10 +169,4 @@ class ModelElement extends AsyncElement {
|
|
|
108
169
|
|
|
109
170
|
customElements.define('pc-model', ModelElement);
|
|
110
171
|
|
|
111
|
-
declare global {
|
|
112
|
-
interface HTMLElementTagNameMap {
|
|
113
|
-
'pc-model': ModelElement;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
172
|
export { ModelElement };
|
package/src/module.ts
CHANGED
|
@@ -43,17 +43,18 @@ class ModuleElement extends HTMLElement {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Returns the promise that settles when the module has loaded. Awaited by the containing
|
|
48
|
+
* `<pc-app>` element before it creates its graphics device.
|
|
49
|
+
*
|
|
50
|
+
* @returns The load promise.
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
_getLoadPromise(): Promise<void> {
|
|
47
54
|
return this.loadPromise;
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
customElements.define('pc-module', ModuleElement);
|
|
52
59
|
|
|
53
|
-
declare global {
|
|
54
|
-
interface HTMLElementTagNameMap {
|
|
55
|
-
'pc-module': ModuleElement;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
60
|
export { ModuleElement };
|
package/src/parse.ts
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* literal, and returns `null` instead of falling back to a default.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import type { Entity } from 'playcanvas';
|
|
20
|
+
import { Color, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
|
|
20
21
|
|
|
21
22
|
import { CSS_COLORS } from './colors';
|
|
22
23
|
|
|
@@ -31,7 +32,7 @@ import { CSS_COLORS } from './colors';
|
|
|
31
32
|
*/
|
|
32
33
|
export const parseComponents = (value: string, count: number): number[] | null => {
|
|
33
34
|
const components = value.trim().split(/\s+/).map(Number);
|
|
34
|
-
if (components.length !== count || components.some(component => !Number.isFinite(component))) {
|
|
35
|
+
if (components.length !== count || components.some((component) => !Number.isFinite(component))) {
|
|
35
36
|
return null;
|
|
36
37
|
}
|
|
37
38
|
return components;
|
|
@@ -77,7 +78,11 @@ export const parseBool = (value: string | null, defaultValue: boolean): boolean
|
|
|
77
78
|
* @param attribute - The attribute name, used in the warning message.
|
|
78
79
|
* @returns The parsed Color object.
|
|
79
80
|
*/
|
|
80
|
-
export const parseColor = <T extends Color | null>(
|
|
81
|
+
export const parseColor = <T extends Color | null>(
|
|
82
|
+
value: string | null,
|
|
83
|
+
defaultValue: T,
|
|
84
|
+
attribute: string
|
|
85
|
+
): Color | T => {
|
|
81
86
|
if (value === null) {
|
|
82
87
|
return cloneDefault(defaultValue);
|
|
83
88
|
}
|
|
@@ -92,7 +97,10 @@ export const parseColor = <T extends Color | null>(value: string | null, default
|
|
|
92
97
|
if (/^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)) {
|
|
93
98
|
let hex = value.slice(1);
|
|
94
99
|
if (hex.length === 3 || hex.length === 4) {
|
|
95
|
-
hex = hex
|
|
100
|
+
hex = hex
|
|
101
|
+
.split('')
|
|
102
|
+
.map((char) => char + char)
|
|
103
|
+
.join('');
|
|
96
104
|
}
|
|
97
105
|
return new Color().fromString(`#${hex}`);
|
|
98
106
|
}
|
|
@@ -103,7 +111,9 @@ export const parseColor = <T extends Color | null>(value: string | null, default
|
|
|
103
111
|
return new Color(components);
|
|
104
112
|
}
|
|
105
113
|
|
|
106
|
-
console.warn(
|
|
114
|
+
console.warn(
|
|
115
|
+
`Invalid value '${value}' for attribute '${attribute}'. Expected a CSS color name, a hex color or 3 or 4 space-separated numbers. Using '${defaultValue}'.`
|
|
116
|
+
);
|
|
107
117
|
return cloneDefault(defaultValue);
|
|
108
118
|
};
|
|
109
119
|
|
|
@@ -113,25 +123,29 @@ export const parseColor = <T extends Color | null>(value: string | null, default
|
|
|
113
123
|
* the value is invalid — the latter also logs a warning listing the valid names.
|
|
114
124
|
*
|
|
115
125
|
* @param value - The attribute value to parse (`null` when the attribute is absent).
|
|
116
|
-
* @param valid - The valid names: an array, or a map whose keys are the valid names.
|
|
126
|
+
* @param valid - The valid names: an array, or a map whose keys are the valid names. Only the keys
|
|
127
|
+
* are read, so the map's value type is unconstrained - engine enums are mostly numeric constants,
|
|
128
|
+
* but some (e.g. `SCALEMODE_BLEND`) are strings.
|
|
117
129
|
* @param defaultValue - The value to use when the attribute is absent or invalid.
|
|
118
130
|
* @param attribute - The attribute name, used in the warning message.
|
|
119
131
|
* @returns The resolved enum name.
|
|
120
132
|
*/
|
|
121
133
|
export const parseEnum = <T extends string>(
|
|
122
134
|
value: string | null,
|
|
123
|
-
valid: readonly T[] | ReadonlyMap<T,
|
|
135
|
+
valid: readonly T[] | ReadonlyMap<T, unknown>,
|
|
124
136
|
defaultValue: T,
|
|
125
137
|
attribute: string
|
|
126
138
|
): T => {
|
|
127
139
|
if (value === null) {
|
|
128
140
|
return defaultValue;
|
|
129
141
|
}
|
|
130
|
-
const names = Array.isArray(valid) ? valid : [...
|
|
142
|
+
const names: readonly T[] = Array.isArray(valid) ? valid : [...valid.keys()];
|
|
131
143
|
if (names.includes(value as T)) {
|
|
132
144
|
return value as T;
|
|
133
145
|
}
|
|
134
|
-
console.warn(
|
|
146
|
+
console.warn(
|
|
147
|
+
`Invalid value '${value}' for attribute '${attribute}'. Valid values: ${names.join(', ')}. Using '${defaultValue}'.`
|
|
148
|
+
);
|
|
135
149
|
return defaultValue;
|
|
136
150
|
};
|
|
137
151
|
|
|
@@ -145,13 +159,19 @@ export const parseEnum = <T extends string>(
|
|
|
145
159
|
* @param attribute - The attribute name, used in the warning message.
|
|
146
160
|
* @returns The parsed number.
|
|
147
161
|
*/
|
|
148
|
-
export const parseNumber = <T extends number | null>(
|
|
162
|
+
export const parseNumber = <T extends number | null>(
|
|
163
|
+
value: string | null,
|
|
164
|
+
defaultValue: T,
|
|
165
|
+
attribute: string
|
|
166
|
+
): number | T => {
|
|
149
167
|
if (value === null) {
|
|
150
168
|
return defaultValue;
|
|
151
169
|
}
|
|
152
170
|
const number = value.trim() === '' ? NaN : Number(value);
|
|
153
171
|
if (!Number.isFinite(number)) {
|
|
154
|
-
console.warn(
|
|
172
|
+
console.warn(
|
|
173
|
+
`Invalid value '${value}' for attribute '${attribute}'. Expected a finite number. Using '${defaultValue}'.`
|
|
174
|
+
);
|
|
155
175
|
return defaultValue;
|
|
156
176
|
}
|
|
157
177
|
return number;
|
|
@@ -168,13 +188,19 @@ export const parseNumber = <T extends number | null>(value: string | null, defau
|
|
|
168
188
|
* @param attribute - The attribute name, used in the warning message.
|
|
169
189
|
* @returns The parsed Quat object.
|
|
170
190
|
*/
|
|
171
|
-
export const parseQuat = <T extends Quat | null>(
|
|
191
|
+
export const parseQuat = <T extends Quat | null>(
|
|
192
|
+
value: string | null,
|
|
193
|
+
defaultValue: T,
|
|
194
|
+
attribute: string
|
|
195
|
+
): Quat | T => {
|
|
172
196
|
if (value === null) {
|
|
173
197
|
return cloneDefault(defaultValue);
|
|
174
198
|
}
|
|
175
199
|
const components = parseComponents(value, 3);
|
|
176
200
|
if (!components) {
|
|
177
|
-
console.warn(
|
|
201
|
+
console.warn(
|
|
202
|
+
`Invalid value '${value}' for attribute '${attribute}'. Expected 3 space-separated numbers. Using '${defaultValue}'.`
|
|
203
|
+
);
|
|
178
204
|
return cloneDefault(defaultValue);
|
|
179
205
|
}
|
|
180
206
|
return new Quat().setFromEulerAngles(components[0], components[1], components[2]);
|
|
@@ -198,7 +224,10 @@ export const parseTags = (value: string | null, defaultValue: string[] = []): st
|
|
|
198
224
|
// caller's default, or a later mutation would write back through it.
|
|
199
225
|
return [...defaultValue];
|
|
200
226
|
}
|
|
201
|
-
return value
|
|
227
|
+
return value
|
|
228
|
+
.split(',')
|
|
229
|
+
.map((tag) => tag.trim())
|
|
230
|
+
.filter((tag) => tag !== '');
|
|
202
231
|
};
|
|
203
232
|
|
|
204
233
|
/**
|
|
@@ -211,13 +240,19 @@ export const parseTags = (value: string | null, defaultValue: string[] = []): st
|
|
|
211
240
|
* @param attribute - The attribute name, used in the warning message.
|
|
212
241
|
* @returns The parsed Vec2 object.
|
|
213
242
|
*/
|
|
214
|
-
export const parseVec2 = <T extends Vec2 | null>(
|
|
243
|
+
export const parseVec2 = <T extends Vec2 | null>(
|
|
244
|
+
value: string | null,
|
|
245
|
+
defaultValue: T,
|
|
246
|
+
attribute: string
|
|
247
|
+
): Vec2 | T => {
|
|
215
248
|
if (value === null) {
|
|
216
249
|
return cloneDefault(defaultValue);
|
|
217
250
|
}
|
|
218
251
|
const components = parseComponents(value, 2);
|
|
219
252
|
if (!components) {
|
|
220
|
-
console.warn(
|
|
253
|
+
console.warn(
|
|
254
|
+
`Invalid value '${value}' for attribute '${attribute}'. Expected 2 space-separated numbers. Using '${defaultValue}'.`
|
|
255
|
+
);
|
|
221
256
|
return cloneDefault(defaultValue);
|
|
222
257
|
}
|
|
223
258
|
return new Vec2(components);
|
|
@@ -233,13 +268,19 @@ export const parseVec2 = <T extends Vec2 | null>(value: string | null, defaultVa
|
|
|
233
268
|
* @param attribute - The attribute name, used in the warning message.
|
|
234
269
|
* @returns The parsed Vec3 object.
|
|
235
270
|
*/
|
|
236
|
-
export const parseVec3 = <T extends Vec3 | null>(
|
|
271
|
+
export const parseVec3 = <T extends Vec3 | null>(
|
|
272
|
+
value: string | null,
|
|
273
|
+
defaultValue: T,
|
|
274
|
+
attribute: string
|
|
275
|
+
): Vec3 | T => {
|
|
237
276
|
if (value === null) {
|
|
238
277
|
return cloneDefault(defaultValue);
|
|
239
278
|
}
|
|
240
279
|
const components = parseComponents(value, 3);
|
|
241
280
|
if (!components) {
|
|
242
|
-
console.warn(
|
|
281
|
+
console.warn(
|
|
282
|
+
`Invalid value '${value}' for attribute '${attribute}'. Expected 3 space-separated numbers. Using '${defaultValue}'.`
|
|
283
|
+
);
|
|
243
284
|
return cloneDefault(defaultValue);
|
|
244
285
|
}
|
|
245
286
|
return new Vec3(components);
|
|
@@ -255,13 +296,19 @@ export const parseVec3 = <T extends Vec3 | null>(value: string | null, defaultVa
|
|
|
255
296
|
* @param attribute - The attribute name, used in the warning message.
|
|
256
297
|
* @returns The parsed Vec4 object.
|
|
257
298
|
*/
|
|
258
|
-
export const parseVec4 = <T extends Vec4 | null>(
|
|
299
|
+
export const parseVec4 = <T extends Vec4 | null>(
|
|
300
|
+
value: string | null,
|
|
301
|
+
defaultValue: T,
|
|
302
|
+
attribute: string
|
|
303
|
+
): Vec4 | T => {
|
|
259
304
|
if (value === null) {
|
|
260
305
|
return cloneDefault(defaultValue);
|
|
261
306
|
}
|
|
262
307
|
const components = parseComponents(value, 4);
|
|
263
308
|
if (!components) {
|
|
264
|
-
console.warn(
|
|
309
|
+
console.warn(
|
|
310
|
+
`Invalid value '${value}' for attribute '${attribute}'. Expected 4 space-separated numbers. Using '${defaultValue}'.`
|
|
311
|
+
);
|
|
265
312
|
return cloneDefault(defaultValue);
|
|
266
313
|
}
|
|
267
314
|
return new Vec4(components);
|
package/src/scene.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Scene } from 'playcanvas';
|
|
2
|
+
import { Color, Vec3 } from 'playcanvas';
|
|
2
3
|
|
|
3
4
|
import { AsyncElement } from './async-element';
|
|
4
5
|
import { parseColor, parseEnum, parseNumber, parseVec3 } from './parse';
|
|
@@ -77,12 +78,20 @@ class SceneElement extends AsyncElement {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
this._scene = app.scene;
|
|
80
|
-
this.
|
|
81
|
+
this._updateSceneSettings();
|
|
81
82
|
|
|
82
83
|
this._onReady();
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
disconnectedCallback() {
|
|
87
|
+
// The scene belongs to the application, and removing this element - or the <pc-app>
|
|
88
|
+
// above it, which disconnects first - parts the two. Re-arm readiness so a re-inserted
|
|
89
|
+
// element announces the scene it acquires then, not the one it lost here.
|
|
90
|
+
this._scene = null;
|
|
91
|
+
this._resetReady();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private _updateSceneSettings() {
|
|
86
95
|
if (this._scene) {
|
|
87
96
|
this._scene.fog.type = this._fog;
|
|
88
97
|
this._scene.fog.color = this._fogColor;
|
|
@@ -251,10 +260,4 @@ class SceneElement extends AsyncElement {
|
|
|
251
260
|
|
|
252
261
|
customElements.define('pc-scene', SceneElement);
|
|
253
262
|
|
|
254
|
-
declare global {
|
|
255
|
-
interface HTMLElementTagNameMap {
|
|
256
|
-
'pc-scene': SceneElement;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
263
|
export { SceneElement };
|
package/src/sky.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Asset,
|
|
1
|
+
import type { Asset, EventHandle, Scene, Texture } from 'playcanvas';
|
|
2
|
+
import { EnvLighting, LAYERID_SKYBOX, Quat, Vec3 } from 'playcanvas';
|
|
2
3
|
|
|
3
|
-
import { AppElement } from './app';
|
|
4
|
+
import type { AppElement } from './app';
|
|
4
5
|
import { AssetElement } from './asset';
|
|
5
6
|
import { AsyncElement } from './async-element';
|
|
6
7
|
import { parseBool, parseEnum, parseNumber, parseVec3 } from './parse';
|
|
@@ -19,7 +20,7 @@ class SkyElement extends AsyncElement {
|
|
|
19
20
|
|
|
20
21
|
private _rotation = new Vec3();
|
|
21
22
|
|
|
22
|
-
private
|
|
23
|
+
private _mipLevel = 0;
|
|
23
24
|
|
|
24
25
|
private _lighting = false;
|
|
25
26
|
|
|
@@ -31,14 +32,36 @@ class SkyElement extends AsyncElement {
|
|
|
31
32
|
|
|
32
33
|
private _appElement: AppElement | null = null;
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Incremented on every new load and on disconnect, and captured by a load when it starts. A
|
|
37
|
+
* load that resumes from an await or a load callback abandons itself if the value has moved
|
|
38
|
+
* on, so a superseded load cannot generate a skybox for a scene it no longer configures.
|
|
39
|
+
*/
|
|
40
|
+
private _loadGeneration = 0;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The pending asset-load subscription of the current load, if it is waiting for its asset.
|
|
44
|
+
* Held so that whatever supersedes the load can detach the handler from the asset, rather
|
|
45
|
+
* than leave it registered until the asset loads (or forever, if it never does).
|
|
46
|
+
*/
|
|
47
|
+
private _loadHandle: EventHandle | null = null;
|
|
48
|
+
|
|
34
49
|
connectedCallback() {
|
|
35
50
|
this._loadSkybox();
|
|
36
51
|
this._onReady();
|
|
37
52
|
}
|
|
38
53
|
|
|
39
54
|
disconnectedCallback() {
|
|
55
|
+
this._loadGeneration++;
|
|
56
|
+
this._detachLoadHandler();
|
|
40
57
|
this._unloadSkybox();
|
|
41
58
|
this._appElement = null;
|
|
59
|
+
this._resetReady();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private _detachLoadHandler() {
|
|
63
|
+
this._loadHandle?.off();
|
|
64
|
+
this._loadHandle = null;
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
private _generateSkybox(asset: Asset) {
|
|
@@ -48,11 +71,18 @@ class SkyElement extends AsyncElement {
|
|
|
48
71
|
|
|
49
72
|
const skybox = EnvLighting.generateSkyboxCubemap(source);
|
|
50
73
|
skybox.anisotropy = 4;
|
|
74
|
+
// This element owns what it generated (see _unloadSkybox) - replacing a skybox from an
|
|
75
|
+
// earlier load must release it, not orphan it on the GPU
|
|
76
|
+
this._scene.skybox?.destroy();
|
|
51
77
|
this._scene.skybox = skybox;
|
|
52
78
|
|
|
53
79
|
if (this._lighting) {
|
|
54
80
|
const lighting = EnvLighting.generateLightingSource(source);
|
|
55
81
|
const envAtlas = EnvLighting.generateAtlas(lighting);
|
|
82
|
+
// The lighting source is an intermediate: the atlas is rendered from it and it is
|
|
83
|
+
// not needed afterwards
|
|
84
|
+
lighting.destroy();
|
|
85
|
+
this._scene.envAtlas?.destroy();
|
|
56
86
|
this._scene.envAtlas = envAtlas;
|
|
57
87
|
}
|
|
58
88
|
|
|
@@ -65,11 +95,21 @@ class SkyElement extends AsyncElement {
|
|
|
65
95
|
this._scene.sky.node.setLocalScale(this._scale);
|
|
66
96
|
this._scene.sky.center = this._center;
|
|
67
97
|
this._scene.skyboxIntensity = this._intensity;
|
|
68
|
-
this._scene.skyboxMip = this.
|
|
98
|
+
this._scene.skyboxMip = this._mipLevel;
|
|
69
99
|
}
|
|
70
100
|
|
|
71
101
|
private async _loadSkybox() {
|
|
102
|
+
// Supersede any load already in flight - only the newest load may generate the skybox
|
|
103
|
+
const generation = ++this._loadGeneration;
|
|
104
|
+
this._detachLoadHandler();
|
|
105
|
+
|
|
72
106
|
const appElement = await this.closestApp?.ready();
|
|
107
|
+
|
|
108
|
+
// The element may have been removed, or another load started, while we waited
|
|
109
|
+
if (generation !== this._loadGeneration) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
73
113
|
const app = appElement?.app;
|
|
74
114
|
if (!appElement || !app) {
|
|
75
115
|
return;
|
|
@@ -87,7 +127,14 @@ class SkyElement extends AsyncElement {
|
|
|
87
127
|
if (asset.loaded) {
|
|
88
128
|
this._generateSkybox(asset);
|
|
89
129
|
} else {
|
|
90
|
-
|
|
130
|
+
// The generation is re-checked even though a superseded handler is detached: the
|
|
131
|
+
// detach relies on how the engine's event emitter treats removal, while the check
|
|
132
|
+
// holds on its own.
|
|
133
|
+
this._loadHandle = asset.once('load', () => {
|
|
134
|
+
this._loadHandle = null;
|
|
135
|
+
if (generation !== this._loadGeneration) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
91
138
|
this._generateSkybox(asset);
|
|
92
139
|
});
|
|
93
140
|
app.assets.load(asset);
|
|
@@ -170,25 +217,6 @@ class SkyElement extends AsyncElement {
|
|
|
170
217
|
return this._intensity;
|
|
171
218
|
}
|
|
172
219
|
|
|
173
|
-
/**
|
|
174
|
-
* Sets the mip level of the skybox.
|
|
175
|
-
* @param value - The mip level.
|
|
176
|
-
*/
|
|
177
|
-
set level(value: number) {
|
|
178
|
-
this._level = value;
|
|
179
|
-
if (this._scene) {
|
|
180
|
-
this._scene.skyboxMip = this._level;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Gets the mip level of the skybox.
|
|
186
|
-
* @returns The mip level.
|
|
187
|
-
*/
|
|
188
|
-
get level() {
|
|
189
|
-
return this._level;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
220
|
/**
|
|
193
221
|
* Sets whether the skybox is used as a light source.
|
|
194
222
|
* @param value - Whether to use lighting.
|
|
@@ -205,6 +233,26 @@ class SkyElement extends AsyncElement {
|
|
|
205
233
|
return this._lighting;
|
|
206
234
|
}
|
|
207
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Sets the mip level of the skybox, where 0 is the sharpest. Raising it selects a blurrier mip,
|
|
238
|
+
* which is how a skybox is softened without blurring the texture itself.
|
|
239
|
+
* @param value - The mip level.
|
|
240
|
+
*/
|
|
241
|
+
set mipLevel(value: number) {
|
|
242
|
+
this._mipLevel = value;
|
|
243
|
+
if (this._scene) {
|
|
244
|
+
this._scene.skyboxMip = this._mipLevel;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Gets the mip level of the skybox.
|
|
250
|
+
* @returns The mip level.
|
|
251
|
+
*/
|
|
252
|
+
get mipLevel() {
|
|
253
|
+
return this._mipLevel;
|
|
254
|
+
}
|
|
255
|
+
|
|
208
256
|
/**
|
|
209
257
|
* Sets the Euler rotation of the skybox.
|
|
210
258
|
* @param value - The rotation.
|
|
@@ -267,7 +315,7 @@ class SkyElement extends AsyncElement {
|
|
|
267
315
|
}
|
|
268
316
|
|
|
269
317
|
static get observedAttributes() {
|
|
270
|
-
return ['asset', 'center', 'intensity', '
|
|
318
|
+
return ['asset', 'center', 'intensity', 'lighting', 'mip-level', 'rotation', 'scale', 'type'];
|
|
271
319
|
}
|
|
272
320
|
|
|
273
321
|
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
|
|
@@ -281,12 +329,12 @@ class SkyElement extends AsyncElement {
|
|
|
281
329
|
case 'intensity':
|
|
282
330
|
this.intensity = parseNumber(newValue, 1, name);
|
|
283
331
|
break;
|
|
284
|
-
case 'level':
|
|
285
|
-
this.level = parseNumber(newValue, 0, name);
|
|
286
|
-
break;
|
|
287
332
|
case 'lighting':
|
|
288
333
|
this.lighting = parseBool(newValue, false);
|
|
289
334
|
break;
|
|
335
|
+
case 'mip-level':
|
|
336
|
+
this.mipLevel = parseNumber(newValue, 0, name);
|
|
337
|
+
break;
|
|
290
338
|
case 'rotation':
|
|
291
339
|
this.rotation = parseVec3(newValue, Vec3.ZERO, name);
|
|
292
340
|
break;
|
|
@@ -302,10 +350,4 @@ class SkyElement extends AsyncElement {
|
|
|
302
350
|
|
|
303
351
|
customElements.define('pc-sky', SkyElement);
|
|
304
352
|
|
|
305
|
-
declare global {
|
|
306
|
-
interface HTMLElementTagNameMap {
|
|
307
|
-
'pc-sky': SkyElement;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
353
|
export { SkyElement };
|