@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
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
import { SoundSlot } from 'playcanvas';
|
|
1
|
+
import type { SoundSlot } from 'playcanvas';
|
|
2
2
|
|
|
3
3
|
import { AssetElement } from '../asset';
|
|
4
4
|
import { AsyncElement } from '../async-element';
|
|
5
|
-
import { SoundComponentElement } from './sound-component';
|
|
6
5
|
import { parseBool, parseNumber } from '../parse';
|
|
7
6
|
|
|
7
|
+
import { SoundComponentElement } from './sound-component';
|
|
8
|
+
|
|
8
9
|
/**
|
|
9
10
|
* The SoundSlotElement interface provides properties and methods for manipulating
|
|
10
11
|
* `<pc-sound>` elements. The SoundSlotElement interface also inherits the properties and
|
|
11
12
|
* methods of the {@link AsyncElement} interface.
|
|
12
13
|
*/
|
|
13
14
|
class SoundSlotElement extends AsyncElement {
|
|
14
|
-
private _asset
|
|
15
|
+
private _asset = '';
|
|
15
16
|
|
|
16
|
-
private _autoPlay
|
|
17
|
+
private _autoPlay = false;
|
|
17
18
|
|
|
18
19
|
private _duration: number | null = null;
|
|
19
20
|
|
|
20
|
-
private _loop
|
|
21
|
+
private _loop = false;
|
|
21
22
|
|
|
22
|
-
private _name
|
|
23
|
+
private _name = '';
|
|
23
24
|
|
|
24
|
-
private _overlap
|
|
25
|
+
private _overlap = false;
|
|
25
26
|
|
|
26
|
-
private _pitch
|
|
27
|
+
private _pitch = 1;
|
|
27
28
|
|
|
28
|
-
private _startTime
|
|
29
|
+
private _startTime = 0;
|
|
29
30
|
|
|
30
|
-
private _volume
|
|
31
|
+
private _volume = 1;
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* The `<pc-sounds>` this slot was added to, captured at connect time.
|
|
@@ -38,20 +39,31 @@ class SoundSlotElement extends AsyncElement {
|
|
|
38
39
|
*/
|
|
39
40
|
private _soundElement: SoundComponentElement | null = null;
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Incremented on every connect and disconnect, and captured by connectedCallback on entry —
|
|
44
|
+
* a resume from an await abandons itself if the value has moved on, so a stale callback can
|
|
45
|
+
* neither act on a torn-down tree nor add its slot alongside a re-inserted element's own
|
|
46
|
+
* callback.
|
|
47
|
+
*/
|
|
48
|
+
private _connectionGeneration = 0;
|
|
49
|
+
|
|
41
50
|
/**
|
|
42
51
|
* The sound slot.
|
|
43
52
|
*/
|
|
44
53
|
soundSlot: SoundSlot | null = null;
|
|
45
54
|
|
|
46
55
|
async connectedCallback() {
|
|
56
|
+
const generation = ++this._connectionGeneration;
|
|
57
|
+
|
|
47
58
|
const soundElement = this.soundElement;
|
|
48
59
|
await soundElement?.ready();
|
|
49
60
|
|
|
50
|
-
// The element may have been removed
|
|
51
|
-
//
|
|
52
|
-
// already be gone - see the
|
|
61
|
+
// The element may have been removed (perhaps re-inserted, which runs a callback of its
|
|
62
|
+
// own), or its parent torn down, while we were waiting. A <pc-app> disconnects before
|
|
63
|
+
// its children, so by the time we resume the component can already be gone - see the
|
|
64
|
+
// matching guard in disconnectedCallback below.
|
|
53
65
|
const component = soundElement?.component;
|
|
54
|
-
if (
|
|
66
|
+
if (generation !== this._connectionGeneration || !component) {
|
|
55
67
|
return;
|
|
56
68
|
}
|
|
57
69
|
|
|
@@ -78,12 +90,16 @@ class SoundSlotElement extends AsyncElement {
|
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
disconnectedCallback() {
|
|
93
|
+
// Invalidate any connectedCallback still suspended on an await
|
|
94
|
+
this._connectionGeneration++;
|
|
95
|
+
|
|
81
96
|
// Uses the cached parent rather than a fresh lookup, since parentElement is already null
|
|
82
97
|
// by now. The component itself is null if the parent <pc-sound> (or the whole <pc-app>) is
|
|
83
98
|
// being torn down — parents disconnect first and have already removed the component.
|
|
84
99
|
this._soundElement?.component?.removeSlot(this._name);
|
|
85
100
|
this._soundElement = null;
|
|
86
101
|
this.soundSlot = null;
|
|
102
|
+
this._resetReady();
|
|
87
103
|
}
|
|
88
104
|
|
|
89
105
|
protected get soundElement(): SoundComponentElement | null {
|
|
@@ -310,10 +326,4 @@ class SoundSlotElement extends AsyncElement {
|
|
|
310
326
|
|
|
311
327
|
customElements.define('pc-sound', SoundSlotElement);
|
|
312
328
|
|
|
313
|
-
declare global {
|
|
314
|
-
interface HTMLElementTagNameMap {
|
|
315
|
-
'pc-sound': SoundSlotElement;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
329
|
export { SoundSlotElement };
|
package/src/entity.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { AppBase
|
|
1
|
+
import type { AppBase } from 'playcanvas';
|
|
2
|
+
import { Entity, Vec3 } from 'playcanvas';
|
|
2
3
|
|
|
4
|
+
import type { AppElement } from './app';
|
|
3
5
|
import { AsyncElement } from './async-element';
|
|
4
6
|
import { parseBool, parseTags, parseVec3 } from './parse';
|
|
5
7
|
|
|
@@ -61,7 +63,7 @@ class EntityElement extends AsyncElement {
|
|
|
61
63
|
/**
|
|
62
64
|
* The pointer event listeners for the entity.
|
|
63
65
|
*/
|
|
64
|
-
private _listeners:
|
|
66
|
+
private _listeners: Record<string, EventListener[]> = {};
|
|
65
67
|
|
|
66
68
|
/**
|
|
67
69
|
* The event types for which an inline `onpointer*` attribute is currently present.
|
|
@@ -75,6 +77,12 @@ class EntityElement extends AsyncElement {
|
|
|
75
77
|
|
|
76
78
|
private _entity: Entity | null = null;
|
|
77
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The application element this entity is registered with, cached at creation time so the
|
|
82
|
+
* entity can be unregistered even once this element has left the DOM.
|
|
83
|
+
*/
|
|
84
|
+
private _appElement: AppElement | null = null;
|
|
85
|
+
|
|
78
86
|
/**
|
|
79
87
|
* The PlayCanvas entity instance. `null` until the element is ready, and again once it has
|
|
80
88
|
* been removed from the document — await {@link whenReady} or the element's `ready()`
|
|
@@ -85,7 +93,14 @@ class EntityElement extends AsyncElement {
|
|
|
85
93
|
return this._entity;
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Creates the backing entity. Called by the containing `<pc-app>` element during its boot
|
|
98
|
+
* sweep, and on connection for elements inserted while the application is already running.
|
|
99
|
+
*
|
|
100
|
+
* @param app - The application to create the entity in.
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
_createEntity(app: AppBase) {
|
|
89
104
|
// Guard against double creation. When a subtree is inserted at runtime (e.g. cloning a
|
|
90
105
|
// `<template>`), an ancestor's connectedCallback eagerly creates descendant entities; the
|
|
91
106
|
// descendants' own connectedCallbacks would otherwise create them a second time.
|
|
@@ -108,9 +123,44 @@ class EntityElement extends AsyncElement {
|
|
|
108
123
|
if (this._tags.length > 0) {
|
|
109
124
|
entity.tags.add(this._tags);
|
|
110
125
|
}
|
|
126
|
+
|
|
127
|
+
// Register with the owning application, which joins engine nodes back to elements by
|
|
128
|
+
// identity (never by name), and hook the entity's destruction. The engine fires 'destroy'
|
|
129
|
+
// for every entity in a destroyed subtree, so the element learns of its entity's death no
|
|
130
|
+
// matter who causes it: this element, an ancestor, the whole application, or a user
|
|
131
|
+
// script calling entity.destroy().
|
|
132
|
+
this._appElement = this.closestApp;
|
|
133
|
+
this._appElement?._registerEntityElement(entity, this);
|
|
134
|
+
entity.once('destroy', this._onEntityDestroy, this);
|
|
111
135
|
}
|
|
112
136
|
|
|
113
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Handles the destruction of the backing entity. Resets the element so a later re-insertion
|
|
139
|
+
* starts clean: `_built` must be cleared alongside `_entity`, or _buildHierarchy would bail
|
|
140
|
+
* and a re-created entity would never be parented. Readiness is re-armed for the same
|
|
141
|
+
* reason — with the entity gone, a resolved ready promise would resume its awaiters against
|
|
142
|
+
* a null `entity`.
|
|
143
|
+
*
|
|
144
|
+
* @param entity - The entity that was destroyed.
|
|
145
|
+
*/
|
|
146
|
+
private _onEntityDestroy(entity: Entity) {
|
|
147
|
+
this._appElement?._unregisterEntityElement(entity);
|
|
148
|
+
this._appElement = null;
|
|
149
|
+
this._entity = null;
|
|
150
|
+
this._built = false;
|
|
151
|
+
this._resetReady();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Parents the backing entity: under the entity of the nearest ancestor `<pc-entity>` when
|
|
156
|
+
* there is one, and under the application root otherwise. Called by the containing `<pc-app>`
|
|
157
|
+
* element once a sweep has created every entity, so a parent's existence never depends on
|
|
158
|
+
* document order.
|
|
159
|
+
*
|
|
160
|
+
* @param app - The application whose root adopts parentless entities.
|
|
161
|
+
* @internal
|
|
162
|
+
*/
|
|
163
|
+
_buildHierarchy(app: AppBase) {
|
|
114
164
|
if (!this.entity || this._built) return;
|
|
115
165
|
this._built = true;
|
|
116
166
|
|
|
@@ -138,41 +188,29 @@ class EntityElement extends AsyncElement {
|
|
|
138
188
|
}
|
|
139
189
|
|
|
140
190
|
// If app is already running, create entity immediately
|
|
141
|
-
if (closestApp.
|
|
191
|
+
if (closestApp._hierarchyReady) {
|
|
142
192
|
const app = closestApp.app!;
|
|
143
193
|
|
|
144
|
-
this.
|
|
145
|
-
this.
|
|
194
|
+
this._createEntity(app);
|
|
195
|
+
this._buildHierarchy(app);
|
|
146
196
|
|
|
147
197
|
// Handle any child entities that might exist
|
|
148
198
|
const childEntities = this.querySelectorAll<EntityElement>('pc-entity');
|
|
149
199
|
childEntities.forEach((child) => {
|
|
150
|
-
child.
|
|
200
|
+
child._createEntity(app);
|
|
151
201
|
});
|
|
152
202
|
childEntities.forEach((child) => {
|
|
153
|
-
child.
|
|
203
|
+
child._buildHierarchy(app);
|
|
154
204
|
});
|
|
155
205
|
}
|
|
156
206
|
}
|
|
157
207
|
|
|
158
208
|
disconnectedCallback() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// on re-insertion, so the descendant would get a fresh entity that is never parented.
|
|
165
|
-
const children = this.querySelectorAll<EntityElement>('pc-entity');
|
|
166
|
-
children.forEach((child) => {
|
|
167
|
-
child._entity = null;
|
|
168
|
-
child._built = false;
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Destroy the entity
|
|
172
|
-
this.entity.destroy();
|
|
173
|
-
this._entity = null;
|
|
174
|
-
this._built = false;
|
|
175
|
-
}
|
|
209
|
+
// Destroying the entity destroys its whole subtree, and the engine fires 'destroy' for
|
|
210
|
+
// every entity in it - so _onEntityDestroy resets this element AND every descendant
|
|
211
|
+
// element before the descendants' own disconnectedCallbacks run. Their entities are null
|
|
212
|
+
// by then, making this call a no-op for them.
|
|
213
|
+
this._entity?.destroy();
|
|
176
214
|
}
|
|
177
215
|
|
|
178
216
|
/**
|
|
@@ -374,7 +412,7 @@ class EntityElement extends AsyncElement {
|
|
|
374
412
|
|
|
375
413
|
removeEventListener(type: string, listener: EventListener, options?: boolean | EventListenerOptions) {
|
|
376
414
|
if (this._listeners[type]) {
|
|
377
|
-
this._listeners[type] = this._listeners[type].filter(l => l !== listener);
|
|
415
|
+
this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
|
|
378
416
|
}
|
|
379
417
|
super.removeEventListener(type, listener, options);
|
|
380
418
|
if (type.startsWith('pointer')) {
|
|
@@ -382,17 +420,20 @@ class EntityElement extends AsyncElement {
|
|
|
382
420
|
}
|
|
383
421
|
}
|
|
384
422
|
|
|
385
|
-
|
|
423
|
+
/**
|
|
424
|
+
* Whether the element has a listener for an event type, registered either with
|
|
425
|
+
* {@link addEventListener} or with the matching inline `onpointer*` attribute. Read by the
|
|
426
|
+
* containing `<pc-app>` element to gate pointer event synthesis.
|
|
427
|
+
*
|
|
428
|
+
* @param type - The event type.
|
|
429
|
+
* @returns Whether a listener is registered.
|
|
430
|
+
* @internal
|
|
431
|
+
*/
|
|
432
|
+
_hasListeners(type: string): boolean {
|
|
386
433
|
return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
|
|
387
434
|
}
|
|
388
435
|
}
|
|
389
436
|
|
|
390
437
|
customElements.define('pc-entity', EntityElement);
|
|
391
438
|
|
|
392
|
-
declare global {
|
|
393
|
-
interface HTMLElementTagNameMap {
|
|
394
|
-
'pc-entity': EntityElement;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
439
|
export { EntityElement };
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module EngineWebComponents
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
/* eslint-disable import/order */
|
|
10
|
+
/* eslint-disable import-x/order */
|
|
11
11
|
|
|
12
12
|
// Note that order matters here (e.g. pc-entity must be defined before components)
|
|
13
13
|
import { AsyncElement, whenReady } from './async-element';
|
|
@@ -40,6 +40,50 @@ import { ModelElement } from './model';
|
|
|
40
40
|
import { SceneElement } from './scene';
|
|
41
41
|
import { SkyElement } from './sky';
|
|
42
42
|
|
|
43
|
+
import type {
|
|
44
|
+
ScriptAttributesChangeEvent,
|
|
45
|
+
ScriptEnableChangeEvent,
|
|
46
|
+
ScriptNameChangeEvent
|
|
47
|
+
} from './components/script-component';
|
|
48
|
+
|
|
49
|
+
declare global {
|
|
50
|
+
interface HTMLElementEventMap {
|
|
51
|
+
scriptattributeschange: ScriptAttributesChangeEvent;
|
|
52
|
+
scriptenablechange: ScriptEnableChangeEvent;
|
|
53
|
+
scriptnamechange: ScriptNameChangeEvent;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface HTMLElementTagNameMap {
|
|
57
|
+
'pc-app': AppElement;
|
|
58
|
+
'pc-asset': AssetElement;
|
|
59
|
+
'pc-button': ButtonComponentElement;
|
|
60
|
+
'pc-camera': CameraComponentElement;
|
|
61
|
+
'pc-collision': CollisionComponentElement;
|
|
62
|
+
'pc-element': ElementComponentElement;
|
|
63
|
+
'pc-entity': EntityElement;
|
|
64
|
+
'pc-gsplat': GSplatComponentElement;
|
|
65
|
+
'pc-layoutchild': LayoutChildComponentElement;
|
|
66
|
+
'pc-layoutgroup': LayoutGroupComponentElement;
|
|
67
|
+
'pc-light': LightComponentElement;
|
|
68
|
+
'pc-listener': ListenerComponentElement;
|
|
69
|
+
'pc-material': MaterialElement;
|
|
70
|
+
'pc-model': ModelElement;
|
|
71
|
+
'pc-module': ModuleElement;
|
|
72
|
+
'pc-particles': ParticleSystemComponentElement;
|
|
73
|
+
'pc-render': RenderComponentElement;
|
|
74
|
+
'pc-rigidbody': RigidBodyComponentElement;
|
|
75
|
+
'pc-scene': SceneElement;
|
|
76
|
+
'pc-screen': ScreenComponentElement;
|
|
77
|
+
'pc-script': ScriptElement;
|
|
78
|
+
'pc-scripts': ScriptComponentElement;
|
|
79
|
+
'pc-scrollbar': ScrollbarComponentElement;
|
|
80
|
+
'pc-scrollview': ScrollViewComponentElement;
|
|
81
|
+
'pc-sky': SkyElement;
|
|
82
|
+
'pc-sound': SoundSlotElement;
|
|
83
|
+
'pc-sounds': SoundComponentElement;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
43
87
|
export {
|
|
44
88
|
AsyncElement,
|
|
45
89
|
ModuleElement,
|
package/src/loading-bar.ts
CHANGED
|
@@ -59,14 +59,14 @@ class LoadingBar {
|
|
|
59
59
|
// aria-valuenow is set, which is what marks a progressbar indeterminate. jsdom has no Web
|
|
60
60
|
// Animations API, so the guard degrades to a static bar there rather than crashing boot.
|
|
61
61
|
if (typeof this._fill.animate === 'function') {
|
|
62
|
-
this._sweep = this._fill.animate(
|
|
63
|
-
{ transform: 'scaleX(0.25) translateX(-100%)' },
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
this._sweep = this._fill.animate(
|
|
63
|
+
[{ transform: 'scaleX(0.25) translateX(-100%)' }, { transform: 'scaleX(0.25) translateX(500%)' }],
|
|
64
|
+
{
|
|
65
|
+
duration: 1000,
|
|
66
|
+
iterations: Infinity,
|
|
67
|
+
easing: 'ease-in-out'
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
package/src/material.ts
CHANGED
|
@@ -21,17 +21,26 @@ import {
|
|
|
21
21
|
SPECOCC_GLOSSDEPENDENT,
|
|
22
22
|
SPECOCC_NONE,
|
|
23
23
|
StandardMaterial,
|
|
24
|
-
Vec2
|
|
25
|
-
type EventHandle,
|
|
26
|
-
type Texture
|
|
24
|
+
Vec2
|
|
27
25
|
} from 'playcanvas';
|
|
26
|
+
import type { EventHandle, Texture } from 'playcanvas';
|
|
28
27
|
|
|
29
|
-
import { AppElement } from './app';
|
|
28
|
+
import type { AppElement } from './app';
|
|
30
29
|
import { AssetElement } from './asset';
|
|
31
30
|
import { parseBool, parseColor, parseEnum, parseNumber, parseVec2 } from './parse';
|
|
32
31
|
|
|
33
|
-
type BlendType =
|
|
34
|
-
|
|
32
|
+
type BlendType =
|
|
33
|
+
| 'none'
|
|
34
|
+
| 'normal'
|
|
35
|
+
| 'additive'
|
|
36
|
+
| 'additive-alpha'
|
|
37
|
+
| 'premultiplied'
|
|
38
|
+
| 'multiplicative'
|
|
39
|
+
| 'multiplicative-2x'
|
|
40
|
+
| 'screen'
|
|
41
|
+
| 'min'
|
|
42
|
+
| 'max'
|
|
43
|
+
| 'subtractive';
|
|
35
44
|
|
|
36
45
|
const blendTypes = new Map<BlendType, number>([
|
|
37
46
|
['none', BLEND_NONE],
|
|
@@ -100,8 +109,8 @@ const roughnessAliases = ['roughness', 'roughness-map'];
|
|
|
100
109
|
* The texture slots a `pc-material` can populate. Each is backed by a `pc-asset` id rather than a
|
|
101
110
|
* `Texture`, so the element can be authored before the asset has loaded.
|
|
102
111
|
*/
|
|
103
|
-
type TextureSlot =
|
|
104
|
-
'metalnessMap' | 'normalMap' | 'opacityMap';
|
|
112
|
+
type TextureSlot =
|
|
113
|
+
'aoMap' | 'diffuseMap' | 'emissiveMap' | 'glossMap' | 'heightMap' | 'metalnessMap' | 'normalMap' | 'opacityMap';
|
|
105
114
|
|
|
106
115
|
/**
|
|
107
116
|
* The MaterialElement interface provides properties and methods for manipulating
|
|
@@ -286,7 +295,7 @@ class MaterialElement extends HTMLElement {
|
|
|
286
295
|
|
|
287
296
|
private _useLighting = true;
|
|
288
297
|
|
|
289
|
-
// Diverges from the engine default of false - see the class docblock and
|
|
298
|
+
// Diverges from the engine default of false - see the class docblock and _createMaterial()
|
|
290
299
|
private _useMetalness = true;
|
|
291
300
|
|
|
292
301
|
private _useMetalnessSpecularColor = false;
|
|
@@ -306,10 +315,14 @@ class MaterialElement extends HTMLElement {
|
|
|
306
315
|
|
|
307
316
|
private _glossConflictWarned = false;
|
|
308
317
|
|
|
318
|
+
/**
|
|
319
|
+
* The material. `null` until the containing application has created it — an element present
|
|
320
|
+
* at startup has its material once the application is ready.
|
|
321
|
+
*/
|
|
309
322
|
material: StandardMaterial | null = null;
|
|
310
323
|
|
|
311
324
|
async connectedCallback() {
|
|
312
|
-
const appElement = this.parentElement?.closest('pc-app') as AppElement | null ?? null;
|
|
325
|
+
const appElement = (this.parentElement?.closest('pc-app') as AppElement | null) ?? null;
|
|
313
326
|
|
|
314
327
|
// Materials must be direct children of pc-app (matches the boot query ':scope > pc-material')
|
|
315
328
|
if (!appElement || this.parentElement !== appElement) {
|
|
@@ -326,11 +339,18 @@ class MaterialElement extends HTMLElement {
|
|
|
326
339
|
// elements inserted (or re-inserted) after the app is already running
|
|
327
340
|
if (!this.material) {
|
|
328
341
|
if (!appElement.app) return; // pc-app is re-connecting; its own boot will create this
|
|
329
|
-
this.
|
|
342
|
+
this._createMaterial();
|
|
330
343
|
}
|
|
331
344
|
}
|
|
332
345
|
|
|
333
|
-
|
|
346
|
+
/**
|
|
347
|
+
* Creates the material from the element's cached properties. Called by the containing
|
|
348
|
+
* `<pc-app>` element during its boot sweep, and on connection for elements inserted while
|
|
349
|
+
* the application is already running.
|
|
350
|
+
*
|
|
351
|
+
* @internal
|
|
352
|
+
*/
|
|
353
|
+
_createMaterial() {
|
|
334
354
|
const material = new StandardMaterial();
|
|
335
355
|
this.material = material;
|
|
336
356
|
|
|
@@ -464,10 +484,10 @@ class MaterialElement extends HTMLElement {
|
|
|
464
484
|
* warning latches and reports once per episode, clearing when the clash is resolved.
|
|
465
485
|
*/
|
|
466
486
|
private _warnGlossConflict() {
|
|
467
|
-
const quote = (names: string[]) => `'${names.join('
|
|
487
|
+
const quote = (names: string[]) => `'${names.join("', '")}'`;
|
|
468
488
|
|
|
469
|
-
const roughness = roughnessAliases.filter(name => this.hasAttribute(name));
|
|
470
|
-
const gloss = glossConflicts.filter(name => this.hasAttribute(name));
|
|
489
|
+
const roughness = roughnessAliases.filter((name) => this.hasAttribute(name));
|
|
490
|
+
const gloss = glossConflicts.filter((name) => this.hasAttribute(name));
|
|
471
491
|
|
|
472
492
|
if (roughness.length === 0 || gloss.length === 0) {
|
|
473
493
|
this._glossConflictWarned = false;
|
|
@@ -477,8 +497,10 @@ class MaterialElement extends HTMLElement {
|
|
|
477
497
|
if (this._glossConflictWarned) return;
|
|
478
498
|
this._glossConflictWarned = true;
|
|
479
499
|
|
|
480
|
-
console.warn(
|
|
481
|
-
'
|
|
500
|
+
console.warn(
|
|
501
|
+
`pc-material '${this.id}' sets both ${quote(roughness)} and ${quote(gloss)} - ` +
|
|
502
|
+
'the roughness-* attributes invert gloss, so the two families contradict each other. Use one or the other.'
|
|
503
|
+
);
|
|
482
504
|
}
|
|
483
505
|
|
|
484
506
|
/**
|
|
@@ -488,7 +510,7 @@ class MaterialElement extends HTMLElement {
|
|
|
488
510
|
* @param id - The id of the `pc-asset`, or an empty string to clear the slot.
|
|
489
511
|
* @param slot - The material property to write.
|
|
490
512
|
*/
|
|
491
|
-
|
|
513
|
+
private _setMap(id: string, slot: TextureSlot) {
|
|
492
514
|
// Drop any load still pending for this slot - its texture is no longer the one we want
|
|
493
515
|
this._mapHandles.get(slot)?.off();
|
|
494
516
|
this._mapHandles.delete(slot);
|
|
@@ -509,10 +531,13 @@ class MaterialElement extends HTMLElement {
|
|
|
509
531
|
return;
|
|
510
532
|
}
|
|
511
533
|
|
|
512
|
-
this._mapHandles.set(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
534
|
+
this._mapHandles.set(
|
|
535
|
+
slot,
|
|
536
|
+
asset.once('load', () => {
|
|
537
|
+
this._mapHandles.delete(slot);
|
|
538
|
+
this._applyMap(slot, asset.resource as Texture);
|
|
539
|
+
})
|
|
540
|
+
);
|
|
516
541
|
}
|
|
517
542
|
|
|
518
543
|
/**
|
|
@@ -592,7 +617,7 @@ class MaterialElement extends HTMLElement {
|
|
|
592
617
|
*/
|
|
593
618
|
set aoMap(value: string) {
|
|
594
619
|
this._aoMap = value;
|
|
595
|
-
this.
|
|
620
|
+
this._setMap(value, 'aoMap');
|
|
596
621
|
}
|
|
597
622
|
|
|
598
623
|
/**
|
|
@@ -850,7 +875,7 @@ class MaterialElement extends HTMLElement {
|
|
|
850
875
|
*/
|
|
851
876
|
set diffuseMap(value: string) {
|
|
852
877
|
this._diffuseMap = value;
|
|
853
|
-
this.
|
|
878
|
+
this._setMap(value, 'diffuseMap');
|
|
854
879
|
}
|
|
855
880
|
|
|
856
881
|
/**
|
|
@@ -1007,7 +1032,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1007
1032
|
*/
|
|
1008
1033
|
set emissiveMap(value: string) {
|
|
1009
1034
|
this._emissiveMap = value;
|
|
1010
|
-
this.
|
|
1035
|
+
this._setMap(value, 'emissiveMap');
|
|
1011
1036
|
}
|
|
1012
1037
|
|
|
1013
1038
|
/**
|
|
@@ -1205,7 +1230,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1205
1230
|
*/
|
|
1206
1231
|
set glossMap(value: string) {
|
|
1207
1232
|
this._glossMap = value;
|
|
1208
|
-
this.
|
|
1233
|
+
this._setMap(value, 'glossMap');
|
|
1209
1234
|
}
|
|
1210
1235
|
|
|
1211
1236
|
/**
|
|
@@ -1322,7 +1347,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1322
1347
|
*/
|
|
1323
1348
|
set heightMap(value: string) {
|
|
1324
1349
|
this._heightMap = value;
|
|
1325
|
-
this.
|
|
1350
|
+
this._setMap(value, 'heightMap');
|
|
1326
1351
|
}
|
|
1327
1352
|
|
|
1328
1353
|
/**
|
|
@@ -1479,7 +1504,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1479
1504
|
*/
|
|
1480
1505
|
set metalnessMap(value: string) {
|
|
1481
1506
|
this._metalnessMap = value;
|
|
1482
|
-
this.
|
|
1507
|
+
this._setMap(value, 'metalnessMap');
|
|
1483
1508
|
}
|
|
1484
1509
|
|
|
1485
1510
|
/**
|
|
@@ -1596,7 +1621,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1596
1621
|
*/
|
|
1597
1622
|
set normalMap(value: string) {
|
|
1598
1623
|
this._normalMap = value;
|
|
1599
|
-
this.
|
|
1624
|
+
this._setMap(value, 'normalMap');
|
|
1600
1625
|
}
|
|
1601
1626
|
|
|
1602
1627
|
/**
|
|
@@ -1694,7 +1719,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1694
1719
|
set occludeDirect(value: boolean) {
|
|
1695
1720
|
this._occludeDirect = value;
|
|
1696
1721
|
if (this.material) {
|
|
1697
|
-
// @ts-ignore see
|
|
1722
|
+
// @ts-ignore see _createMaterial() - the engine mistypes occludeDirect as a number
|
|
1698
1723
|
this.material.occludeDirect = value;
|
|
1699
1724
|
this._scheduleUpdate();
|
|
1700
1725
|
}
|
|
@@ -1796,7 +1821,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1796
1821
|
*/
|
|
1797
1822
|
set opacityMap(value: string) {
|
|
1798
1823
|
this._opacityMap = value;
|
|
1799
|
-
this.
|
|
1824
|
+
this._setMap(value, 'opacityMap');
|
|
1800
1825
|
}
|
|
1801
1826
|
|
|
1802
1827
|
/**
|
|
@@ -2150,6 +2175,13 @@ class MaterialElement extends HTMLElement {
|
|
|
2150
2175
|
return this._useTonemap;
|
|
2151
2176
|
}
|
|
2152
2177
|
|
|
2178
|
+
/**
|
|
2179
|
+
* Returns the {@link StandardMaterial} created by the `<pc-material>` element with the given
|
|
2180
|
+
* `id`, or `undefined` if there is no such element or its material has not been created yet.
|
|
2181
|
+
*
|
|
2182
|
+
* @param id - The `id` of the `<pc-material>` element.
|
|
2183
|
+
* @returns The material, or `undefined`.
|
|
2184
|
+
*/
|
|
2153
2185
|
static get(id: string) {
|
|
2154
2186
|
const materialElement = document.querySelector<MaterialElement>(`pc-material[id="${id}"]`);
|
|
2155
2187
|
return materialElement?.material;
|
|
@@ -2508,10 +2540,4 @@ class MaterialElement extends HTMLElement {
|
|
|
2508
2540
|
|
|
2509
2541
|
customElements.define('pc-material', MaterialElement);
|
|
2510
2542
|
|
|
2511
|
-
declare global {
|
|
2512
|
-
interface HTMLElementTagNameMap {
|
|
2513
|
-
'pc-material': MaterialElement;
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
2543
|
export { MaterialElement };
|