@@ -43,7 +43,11 @@
|
|
43
43
|
return ref;
|
44
44
|
}
|
45
45
|
|
46
|
-
|
46
|
+
dispose() {
|
47
|
+
for (let key in this._assetReferences) {
|
48
|
+
const ref = this._assetReferences[key];
|
49
|
+
ref?.unload();
|
50
|
+
}
|
47
51
|
this._assetReferences = {};
|
48
52
|
}
|
49
53
|
|
@@ -386,7 +386,7 @@
|
|
386
386
|
// which is probably not desired if it is set via the skybox-image attribute
|
387
387
|
destroy(this.scene, true, true);
|
388
388
|
this.scene = new Scene();
|
389
|
-
this.addressables?.
|
389
|
+
this.addressables?.dispose();
|
390
390
|
this.lightmaps?.clear();
|
391
391
|
this.physics?.engine?.clearCaches();
|
392
392
|
|
@@ -416,7 +416,9 @@
|
|
416
416
|
if (!this.isManagedExternally) {
|
417
417
|
this.renderer.dispose();
|
418
418
|
}
|
419
|
+
this.scene = null!;
|
419
420
|
this.renderer = null!;
|
421
|
+
this.input.dispose();
|
420
422
|
for (const cb of this._disposeCallbacks) {
|
421
423
|
try {
|
422
424
|
cb();
|
@@ -601,10 +603,14 @@
|
|
601
603
|
|
602
604
|
this.internalOnUpdateVisible();
|
603
605
|
|
604
|
-
|
606
|
+
if (!this.renderer) {
|
607
|
+
if(debug) console.warn("Context has no renderer (perhaps it was disconnected?", this.domElement.isConnected);
|
608
|
+
return false;
|
609
|
+
}
|
605
610
|
|
606
|
-
if (!this.isManagedExternally && !this.domElement.shadowRoot)
|
611
|
+
if (!this.isManagedExternally && !this.domElement.shadowRoot) {
|
607
612
|
this.domElement.prepend(this.renderer.domElement);
|
613
|
+
}
|
608
614
|
|
609
615
|
Context.Current = this;
|
610
616
|
|
@@ -23,6 +23,9 @@
|
|
23
23
|
/** override the default ktx2 decoder path */
|
24
24
|
"ktx2DecoderPath"?: string;
|
25
25
|
|
26
|
+
/** Add to prevent Needle Engine context from being disposed when the element is removed from the DOM */
|
27
|
+
"keep-alive"? : boolean;
|
28
|
+
|
26
29
|
addEventListener(event: "ready", callback: (event: CustomEvent) => void): void;
|
27
30
|
addEventListener(event: "error", callback: (event: CustomEvent) => void): void;
|
28
31
|
}
|
@@ -96,10 +96,10 @@
|
|
96
96
|
this.addEventListener("error", this.onError);
|
97
97
|
}
|
98
98
|
|
99
|
+
|
99
100
|
async connectedCallback() {
|
100
101
|
if (debug) {
|
101
102
|
console.log("<needle-engine> connected");
|
102
|
-
console.dir(this);
|
103
103
|
}
|
104
104
|
|
105
105
|
this.onSetupDesktop();
|
@@ -111,22 +111,28 @@
|
|
111
111
|
this.setAttribute("src", global);
|
112
112
|
}
|
113
113
|
}
|
114
|
-
|
114
|
+
|
115
115
|
// we have to wait because codegen does set the src attribute when it's loaded
|
116
116
|
// which might happen after the element is connected
|
117
117
|
// if the `src` is then still null we want to initialize the default scene
|
118
|
+
const loadId = this._loadId;
|
118
119
|
setTimeout(() => {
|
119
120
|
if (this.isConnected === false) return;
|
120
|
-
|
121
|
-
|
122
|
-
this.onLoad();
|
123
|
-
}
|
121
|
+
if (loadId !== this._loadId) return;
|
122
|
+
this.onLoad();
|
124
123
|
}, 1);
|
125
124
|
}
|
126
125
|
|
127
126
|
disconnectedCallback() {
|
128
|
-
|
129
|
-
|
127
|
+
const keepAlive = this.getAttribute("keep-alive");
|
128
|
+
if (debug) console.warn("<needle-engine> disconnected, keep-alive:", keepAlive);
|
129
|
+
if (keepAlive === undefined || keepAlive !== "true") {
|
130
|
+
console.warn("<needle-engine> dispose!");
|
131
|
+
this._context?.dispose();
|
132
|
+
this._context = null!;
|
133
|
+
this._lastSourceFiles = null;
|
134
|
+
this._loadId += 1;
|
135
|
+
}
|
130
136
|
}
|
131
137
|
|
132
138
|
attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
|
@@ -173,7 +179,13 @@
|
|
173
179
|
|
174
180
|
private async onLoad() {
|
175
181
|
|
182
|
+
if (!this.isConnected) return;
|
176
183
|
if (!this._context) {
|
184
|
+
if(debug) console.warn("Create new context");
|
185
|
+
this._context = new Context({ domElement: this });
|
186
|
+
}
|
187
|
+
|
188
|
+
if (!this._context) {
|
177
189
|
console.error("Needle Engine: Context not initialized");
|
178
190
|
return;
|
179
191
|
}
|
@@ -215,7 +215,7 @@
|
|
215
215
|
}
|
216
216
|
|
217
217
|
private _pointerIsActive(index: number) {
|
218
|
-
if(index < 0) return false;
|
218
|
+
if (index < 0) return false;
|
219
219
|
return this._pointerPressed[index] || this._pointerDown[index] || this._pointerUp[index];
|
220
220
|
}
|
221
221
|
|
@@ -330,45 +330,52 @@
|
|
330
330
|
constructor(context: Context) {
|
331
331
|
super();
|
332
332
|
this.context = context;
|
333
|
-
this.context.post_render_callbacks.push(this.onEndOfFrame
|
333
|
+
this.context.post_render_callbacks.push(this.onEndOfFrame);
|
334
334
|
|
335
|
-
|
335
|
+
window.addEventListener('touchstart', this.onTouchStart, false);
|
336
|
+
window.addEventListener('touchmove', this.onTouchMove, { passive: true });
|
337
|
+
window.addEventListener('touchend', this.onTouchUp, false);
|
336
338
|
|
337
|
-
|
338
|
-
|
339
|
-
|
339
|
+
window.addEventListener('mousedown', this.onMouseDown, false);
|
340
|
+
window.addEventListener('mousemove', this.onMouseMove, false);
|
341
|
+
window.addEventListener('mouseup', this.onMouseUp, false);
|
342
|
+
window.addEventListener('wheel', this.onMouseWheel, { passive: true });
|
340
343
|
|
341
|
-
window.addEventListener(
|
342
|
-
window.addEventListener(
|
343
|
-
window.addEventListener(
|
344
|
+
window.addEventListener("keydown", this.onKeyDown, false);
|
345
|
+
window.addEventListener("keypress", this.onKeyPressed, false);
|
346
|
+
window.addEventListener("keyup", this.onKeyUp, false);
|
344
347
|
|
345
|
-
|
346
|
-
window.addEventListener('
|
347
|
-
|
348
|
-
window.addEventListener('wheel', this.onMouseWheel.bind(this), { passive: true });
|
348
|
+
// e.g. when using sharex to capture we loose focus thus dont get e.g. key up events
|
349
|
+
window.addEventListener('blur', this.onLostFocus);
|
350
|
+
}
|
349
351
|
|
350
|
-
|
351
|
-
|
352
|
-
|
352
|
+
dispose() {
|
353
|
+
const index = this.context.post_render_callbacks.indexOf(this.onEndOfFrame);
|
354
|
+
if (index >= 0) this.context.post_render_callbacks.splice(index, 1);
|
353
355
|
|
354
|
-
|
355
|
-
window.
|
356
|
+
window.removeEventListener('touchstart', this.onTouchStart, false);
|
357
|
+
window.removeEventListener('touchmove', this.onTouchMove, false);
|
358
|
+
window.removeEventListener('touchend', this.onTouchUp, false);
|
356
359
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
360
|
+
window.removeEventListener('mousedown', this.onMouseDown, false);
|
361
|
+
window.removeEventListener('mousemove', this.onMouseMove, false);
|
362
|
+
window.removeEventListener('mouseup', this.onMouseUp, false);
|
363
|
+
window.removeEventListener('wheel', this.onMouseWheel, false);
|
364
|
+
|
365
|
+
window.removeEventListener("keydown", this.onKeyDown, false);
|
366
|
+
window.removeEventListener("keypress", this.onKeyPressed, false);
|
367
|
+
window.removeEventListener("keyup", this.onKeyUp, false);
|
368
|
+
|
369
|
+
window.removeEventListener('blur', this.onLostFocus);
|
363
370
|
}
|
364
371
|
|
365
|
-
private onLostFocus() {
|
372
|
+
private onLostFocus = () => {
|
366
373
|
for (const kp in this.keysPressed) {
|
367
374
|
this.keysPressed[kp].pressed = false;
|
368
375
|
}
|
369
376
|
}
|
370
377
|
|
371
|
-
private onEndOfFrame() {
|
378
|
+
private onEndOfFrame = () => {
|
372
379
|
for (let i = 0; i < this._pointerUp.length; i++)
|
373
380
|
this._pointerUp[i] = false;
|
374
381
|
for (let i = 0; i < this._pointerDown.length; i++)
|
@@ -385,7 +392,7 @@
|
|
385
392
|
this._mouseWheelDeltaY[i] = 0;
|
386
393
|
}
|
387
394
|
|
388
|
-
private canReceiveInput(evt
|
395
|
+
private canReceiveInput(evt: Event) {
|
389
396
|
// If the user has HTML objects ontop of the canvas
|
390
397
|
// if(evt.target === this.context.renderer.domElement) return true;
|
391
398
|
// const css = window.getComputedStyle(evt.target as HTMLElement);
|
@@ -395,7 +402,7 @@
|
|
395
402
|
|
396
403
|
private keysPressed: { [key: KeyCode | string]: { pressed: boolean, frame: number, startFrame: number, key: string, code: KeyCode | string } } = {};
|
397
404
|
|
398
|
-
private onKeyDown(evt: KeyboardEvent) {
|
405
|
+
private onKeyDown = (evt: KeyboardEvent) => {
|
399
406
|
if (!this.context.application.hasFocus)
|
400
407
|
return;
|
401
408
|
const ex = this.keysPressed[evt.code];
|
@@ -403,7 +410,7 @@
|
|
403
410
|
this.keysPressed[evt.code] = { pressed: true, frame: this.context.time.frameCount + 1, startFrame: this.context.time.frameCount + 1, key: evt.key, code: evt.code };
|
404
411
|
this.onDispatchEvent(InputEvents.KeyDown, new KeyEventArgs(evt));
|
405
412
|
}
|
406
|
-
private onKeyPressed(evt: KeyboardEvent) {
|
413
|
+
private onKeyPressed = (evt: KeyboardEvent) => {
|
407
414
|
if (!this.context.application.hasFocus)
|
408
415
|
return;
|
409
416
|
const p = this.keysPressed[evt.code];
|
@@ -413,7 +420,7 @@
|
|
413
420
|
this.onDispatchEvent(InputEvents.KeyPressed, new KeyEventArgs(evt));
|
414
421
|
|
415
422
|
}
|
416
|
-
private onKeyUp(evt: KeyboardEvent) {
|
423
|
+
private onKeyUp = (evt: KeyboardEvent) => {
|
417
424
|
if (!this.context.application.hasFocus)
|
418
425
|
return;
|
419
426
|
const p = this.keysPressed[evt.code];
|
@@ -423,8 +430,8 @@
|
|
423
430
|
this.onDispatchEvent(InputEvents.KeyUp, new KeyEventArgs(evt));
|
424
431
|
}
|
425
432
|
|
426
|
-
private onMouseWheel(evt: WheelEvent) {
|
427
|
-
if(this.canReceiveInput(evt) === false) return;
|
433
|
+
private onMouseWheel = (evt: WheelEvent) => {
|
434
|
+
if (this.canReceiveInput(evt) === false) return;
|
428
435
|
if (this._mouseWheelDeltaY.length <= 0) this._mouseWheelDeltaY.push(0);
|
429
436
|
if (this._mouseWheelChanged.length <= 0) this._mouseWheelChanged.push(false);
|
430
437
|
this._mouseWheelChanged[0] = true;
|
@@ -432,9 +439,9 @@
|
|
432
439
|
this._mouseWheelDeltaY[0] = current + evt.deltaY;
|
433
440
|
}
|
434
441
|
|
435
|
-
private onTouchStart(evt: TouchEvent) {
|
442
|
+
private onTouchStart = (evt: TouchEvent) => {
|
436
443
|
if (evt.changedTouches.length <= 0) return;
|
437
|
-
if(this.canReceiveInput(evt) === false) return;
|
444
|
+
if (this.canReceiveInput(evt) === false) return;
|
438
445
|
for (let i = 0; i < evt.changedTouches.length; i++) {
|
439
446
|
const touch = evt.changedTouches[i];
|
440
447
|
const id = this.getPointerIndex(touch.identifier)
|
@@ -445,7 +452,7 @@
|
|
445
452
|
}
|
446
453
|
}
|
447
454
|
|
448
|
-
private onTouchMove(evt: TouchEvent) {
|
455
|
+
private onTouchMove = (evt: TouchEvent) => {
|
449
456
|
if (evt.changedTouches.length <= 0) return;
|
450
457
|
for (let i = 0; i < evt.changedTouches.length; i++) {
|
451
458
|
const touch = evt.changedTouches[i];
|
@@ -455,7 +462,7 @@
|
|
455
462
|
}
|
456
463
|
}
|
457
464
|
|
458
|
-
private onTouchUp(evt: TouchEvent) {
|
465
|
+
private onTouchUp = (evt: TouchEvent) => {
|
459
466
|
if (evt.changedTouches.length <= 0) return;
|
460
467
|
for (let i = 0; i < evt.changedTouches.length; i++) {
|
461
468
|
const touch = evt.changedTouches[i];
|
@@ -469,21 +476,21 @@
|
|
469
476
|
}
|
470
477
|
}
|
471
478
|
|
472
|
-
private onMouseDown(evt: MouseEvent) {
|
479
|
+
private onMouseDown = (evt: MouseEvent) => {
|
473
480
|
if (evt.defaultPrevented) return;
|
474
|
-
if(this.canReceiveInput(evt) === false) return;
|
481
|
+
if (this.canReceiveInput(evt) === false) return;
|
475
482
|
let i = evt.button;
|
476
483
|
this.onDown({ button: i, clientX: evt.clientX, clientY: evt.clientY, pointerType: PointerType.Mouse, source: evt });
|
477
484
|
}
|
478
485
|
|
479
|
-
private onMouseMove(evt: MouseEvent) {
|
486
|
+
private onMouseMove = (evt: MouseEvent) => {
|
480
487
|
if (evt.defaultPrevented) return;
|
481
488
|
let i = evt.button;
|
482
489
|
const args: PointerEventArgs = { button: i, clientX: evt.clientX, clientY: evt.clientY, pointerType: PointerType.Mouse, source: evt, movementX: evt.movementX, movementY: evt.movementY };
|
483
490
|
this.onMove(args);
|
484
491
|
}
|
485
492
|
|
486
|
-
private onMouseUp(evt: MouseEvent) {
|
493
|
+
private onMouseUp = (evt: MouseEvent) => {
|
487
494
|
if (evt.defaultPrevented) return;
|
488
495
|
let i = evt.button;
|
489
496
|
if (!this.isNewEvent(evt.timeStamp, i, this._pointerUpTimestamp)) return;
|
@@ -504,7 +511,7 @@
|
|
504
511
|
const px = e.clientX;
|
505
512
|
const py = e.clientY;
|
506
513
|
const isInRect = px >= rect.x && px <= rect.right && py >= rect.y && py <= rect.bottom;
|
507
|
-
if(debug && !isInRect) console.log("Not in rect", rect, px, py);
|
514
|
+
if (debug && !isInRect) console.log("Not in rect", rect, px, py);
|
508
515
|
return isInRect;
|
509
516
|
|
510
517
|
}
|
@@ -518,7 +525,7 @@
|
|
518
525
|
const upTime = this._pointerUpTimestamp[evt.button];
|
519
526
|
if (upTime > 0 && upTime === evt.source?.timeStamp) {
|
520
527
|
// we received an UP event for a touch, ignore this DOWN event
|
521
|
-
if(debug) console.log("Ignoring mouse.down for touch.up");
|
528
|
+
if (debug) console.log("Ignoring mouse.down for touch.up");
|
522
529
|
return;
|
523
530
|
}
|
524
531
|
}
|
@@ -678,7 +685,7 @@
|
|
678
685
|
}
|
679
686
|
}
|
680
687
|
|
681
|
-
export declare type KeyCode =
|
688
|
+
export declare type KeyCode =
|
682
689
|
| "Tab"
|
683
690
|
| "Enter"
|
684
691
|
| "ShiftLeft"
|
@@ -38,7 +38,7 @@
|
|
38
38
|
|
39
39
|
async function showLicenseInfo(ctx: IContext) {
|
40
40
|
try {
|
41
|
-
if (
|
41
|
+
if (hasCommercialLicense() !== true) return onNonCommercialVersionDetected(ctx);
|
42
42
|
}
|
43
43
|
catch (err) {
|
44
44
|
if (debug) console.log("License check failed", err)
|
@@ -455,8 +455,10 @@
|
|
455
455
|
|
456
456
|
clearCaches() {
|
457
457
|
this._meshCache.clear();
|
458
|
-
this.eventQueue?.
|
459
|
-
|
458
|
+
if (this.eventQueue?.raw)
|
459
|
+
this.eventQueue?.free();
|
460
|
+
if (this.world?.bodies)
|
461
|
+
this.world?.free();
|
460
462
|
}
|
461
463
|
|
462
464
|
async addBoxCollider(collider: ICollider, center: Vector3, size: Vector3) {
|
@@ -549,6 +551,7 @@
|
|
549
551
|
if (Math.abs(scale.x - 1) > 0.0001 || Math.abs(scale.y - 1) > 0.0001 || Math.abs(scale.z - 1) > 0.0001) {
|
550
552
|
const key = geo.uuid + "_" + scale.x + "_" + scale.y + "_" + scale.z + "_" + convex;
|
551
553
|
if (this._meshCache.has(key)) {
|
554
|
+
if(debugPhysics) console.warn("Use cached mesh collider")
|
552
555
|
positions = this._meshCache.get(key)!;
|
553
556
|
}
|
554
557
|
else {
|
@@ -74,13 +74,17 @@
|
|
74
74
|
}
|
75
75
|
return null;
|
76
76
|
}
|
77
|
+
async function disposeCachedTexture(tex: Promise<Texture>) {
|
78
|
+
const texture = await tex;
|
79
|
+
setDisposable(texture, true);
|
80
|
+
disposeObjectResources(texture);
|
81
|
+
}
|
77
82
|
function registerLoadedTexture(src: string, texture: Promise<Texture>) {
|
78
83
|
const cache = ensureGlobalCache();
|
79
84
|
// Make sure the cache doesnt get too big
|
80
85
|
while (cache.length > 5) {
|
81
86
|
const entry = cache.shift();
|
82
|
-
entry
|
83
|
-
disposeObjectResources(entry?.texture);
|
87
|
+
if (entry) { disposeCachedTexture(entry.texture); }
|
84
88
|
}
|
85
89
|
texture.then(t => setDisposable(t, false));
|
86
90
|
cache.push({ src, texture });
|