@needle-tools/engine 2.63.3-pre.1 → 2.64.0-pre
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/CHANGELOG.md +13 -0
- package/dist/needle-engine.js +7725 -7641
- package/dist/needle-engine.umd.cjs +215 -214
- package/lib/engine/engine_constants.d.ts +1 -1
- package/lib/engine/engine_constants.js +1 -1
- package/lib/engine/engine_constants.js.map +1 -1
- package/lib/engine/engine_gameobject.js +2 -2
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_instancing.d.ts +1 -0
- package/lib/engine/engine_instancing.js +3 -2
- package/lib/engine/engine_instancing.js.map +1 -1
- package/lib/engine/engine_license.js +14 -13
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_mainloop_utils.js +27 -52
- package/lib/engine/engine_mainloop_utils.js.map +1 -1
- package/lib/engine/engine_physics.d.ts +1 -0
- package/lib/engine/engine_physics.js +30 -0
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/engine_setup.d.ts +1 -1
- package/lib/engine/engine_setup.js.map +1 -1
- package/lib/engine/engine_time.d.ts +1 -0
- package/lib/engine/engine_time.js +5 -1
- package/lib/engine/engine_time.js.map +1 -1
- package/lib/engine/engine_types.d.ts +12 -0
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine-components/Animation.js +6 -6
- package/lib/engine-components/Animation.js.map +1 -1
- package/lib/engine-components/AnimatorController.js +2 -0
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/AudioSource.js +1 -1
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/Component.d.ts +1 -0
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ParticleSystem.js +13 -1
- package/lib/engine-components/ParticleSystem.js.map +1 -1
- package/lib/engine-components/Renderer.d.ts +5 -1
- package/lib/engine-components/Renderer.js +78 -29
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/js-extensions/Object3D.js +7 -0
- package/lib/engine-components/js-extensions/Object3D.js.map +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +5 -0
- package/lib/engine-components/timeline/PlayableDirector.js +36 -23
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +9 -0
- package/lib/engine-components/timeline/TimelineTracks.js +112 -19
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/engine/engine_constants.ts +1 -1
- package/src/engine/engine_gameobject.ts +2 -2
- package/src/engine/engine_instancing.ts +3 -3
- package/src/engine/engine_license.ts +14 -13
- package/src/engine/engine_mainloop_utils.ts +32 -58
- package/src/engine/engine_physics.ts +23 -0
- package/src/engine/engine_setup.ts +1 -1
- package/src/engine/engine_time.ts +9 -4
- package/src/engine/engine_types.ts +36 -22
- package/src/engine-components/Animation.ts +6 -5
- package/src/engine-components/AnimatorController.ts +1 -0
- package/src/engine-components/AudioSource.ts +1 -1
- package/src/engine-components/Component.ts +3 -0
- package/src/engine-components/ParticleSystem.ts +15 -2
- package/src/engine-components/Renderer.ts +92 -33
- package/src/engine-components/js-extensions/Object3D.ts +7 -1
- package/src/engine-components/timeline/PlayableDirector.ts +35 -24
- package/src/engine-components/timeline/TimelineTracks.ts +124 -20
- package/src/engine/codegen/license.js +0 -1
|
@@ -5,7 +5,7 @@ import { RendererLightmap } from "./RendererLightmap";
|
|
|
5
5
|
import { Context, FrameEvent } from "../engine/engine_setup";
|
|
6
6
|
import { getParam } from "../engine/engine_utils";
|
|
7
7
|
import { serializable } from "../engine/engine_serialization_decorator";
|
|
8
|
-
import { AxesHelper, Material, Mesh, Object3D, SkinnedMesh, Texture, Vector4 } from "three";
|
|
8
|
+
import { AxesHelper, Material, Matrix4, Mesh, Object3D, SkinnedMesh, Texture, Vector4 } from "three";
|
|
9
9
|
import { NEEDLE_render_objects } from "../engine/extensions/NEEDLE_render_objects";
|
|
10
10
|
import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive";
|
|
11
11
|
import { NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing";
|
|
@@ -18,6 +18,7 @@ import { showBalloonWarning } from "../engine/debug/debug";
|
|
|
18
18
|
// for staying compatible with old code
|
|
19
19
|
export { InstancingUtil } from "../engine/engine_instancing";
|
|
20
20
|
|
|
21
|
+
const debugRenderer = getParam("debugrenderer");
|
|
21
22
|
const suppressInstancing = getParam("noInstancing");
|
|
22
23
|
const debugLightmap = getParam("debuglightmaps") ? true : false;
|
|
23
24
|
const debugInstancing = getParam("debuginstancing");
|
|
@@ -294,6 +295,7 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
awake() {
|
|
298
|
+
if (debugRenderer) console.log("Renderer ", this.name, this);
|
|
297
299
|
this.clearInstancingState();
|
|
298
300
|
|
|
299
301
|
if (this.probeAnchor && debug) this.probeAnchor.add(new AxesHelper(.2));
|
|
@@ -332,6 +334,9 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
332
334
|
if (this.renderOrder !== undefined && this.renderOrder.length > 0)
|
|
333
335
|
this.gameObject.renderOrder = this.renderOrder[0];
|
|
334
336
|
}
|
|
337
|
+
else {
|
|
338
|
+
this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree.bind(this));
|
|
339
|
+
}
|
|
335
340
|
|
|
336
341
|
if (this.lightmapIndex >= 0) {
|
|
337
342
|
// use the override lightmap if its not undefined
|
|
@@ -394,20 +399,14 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
394
399
|
this.handles = undefined;
|
|
395
400
|
this.prevLayers = undefined;
|
|
396
401
|
}
|
|
402
|
+
|
|
397
403
|
setInstancingEnabled(enabled: boolean): boolean {
|
|
398
404
|
if (this._isInstancingEnabled === enabled) return enabled && (this.handles === undefined || this.handles != null && this.handles.length > 0);
|
|
399
405
|
this._isInstancingEnabled = enabled;
|
|
400
406
|
if (enabled) {
|
|
401
|
-
// if handles is undefined we
|
|
402
407
|
if (this.handles === undefined) {
|
|
403
|
-
this.handles = instancing.setup(this.gameObject, this.context, null, { rend: this, foundMeshes: 0 });
|
|
408
|
+
this.handles = instancing.setup(this, this.gameObject, this.context, null, { rend: this, foundMeshes: 0, useMatrixWorldAutoUpdate: this.useInstanceMatrixWorldAutoUpdate() });
|
|
404
409
|
if (this.handles) {
|
|
405
|
-
// const disableSelf = this.gameObject.type === "Mesh" || this.gameObject.children?.length === this.handles.length;
|
|
406
|
-
// this.gameObject.visible = !disableSelf;
|
|
407
|
-
// this.gameObject.type = "Object3D";
|
|
408
|
-
// this.gameObject.material = null;
|
|
409
|
-
// console.log("Using instancing", this.gameObject.visible);
|
|
410
|
-
// this.gameObject.onBeforeRender = () => console.log("SHOULD NOT BE CALLED");
|
|
411
410
|
GameObject.markAsInstancedRendered(this.gameObject, true);
|
|
412
411
|
return true;
|
|
413
412
|
}
|
|
@@ -417,8 +416,6 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
417
416
|
handler.updateInstanceMatrix(true);
|
|
418
417
|
handler.add();
|
|
419
418
|
}
|
|
420
|
-
// this.gameObject.type = "Object3D";
|
|
421
|
-
// this.gameObject.visible = false;
|
|
422
419
|
GameObject.markAsInstancedRendered(this.gameObject, true);
|
|
423
420
|
return true;
|
|
424
421
|
}
|
|
@@ -429,13 +426,19 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
429
426
|
handler.remove();
|
|
430
427
|
}
|
|
431
428
|
}
|
|
432
|
-
// this.gameObject.visible = true;
|
|
433
429
|
return true;
|
|
434
430
|
}
|
|
435
431
|
|
|
436
432
|
return false;
|
|
437
433
|
}
|
|
438
434
|
|
|
435
|
+
/** Return true to wrap matrix update events for instanced rendering to update instance matrices automatically when matrixWorld changes
|
|
436
|
+
* This is a separate method to be overrideable from user code
|
|
437
|
+
*/
|
|
438
|
+
useInstanceMatrixWorldAutoUpdate() {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
439
442
|
start() {
|
|
440
443
|
if (this.enableInstancing && !suppressInstancing) {
|
|
441
444
|
this.setInstancingEnabled(true);
|
|
@@ -461,6 +464,7 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
461
464
|
}
|
|
462
465
|
|
|
463
466
|
this.updateReflectionProbe();
|
|
467
|
+
|
|
464
468
|
}
|
|
465
469
|
|
|
466
470
|
onDisable() {
|
|
@@ -479,17 +483,12 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
479
483
|
NEEDLE_render_objects.applyStencil(this);
|
|
480
484
|
}
|
|
481
485
|
|
|
482
|
-
static envmap: THREE.Texture | null = null;
|
|
483
486
|
|
|
484
487
|
onBeforeRender() {
|
|
485
488
|
if (!this.gameObject) {
|
|
486
489
|
return;
|
|
487
490
|
}
|
|
488
491
|
|
|
489
|
-
Renderer.envmap = this.scene.environment;
|
|
490
|
-
|
|
491
|
-
const needsUpdate: boolean = this.gameObject[NEED_UPDATE_INSTANCE_KEY] === true || this.gameObject.matrixWorldNeedsUpdate;
|
|
492
|
-
|
|
493
492
|
if (this.isMultiMaterialObject(this.gameObject) && this.gameObject.children?.length > 0) {
|
|
494
493
|
for (const ch of this.gameObject.children) {
|
|
495
494
|
this.applySettings(ch);
|
|
@@ -499,9 +498,14 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
499
498
|
this.applySettings(this.gameObject);
|
|
500
499
|
}
|
|
501
500
|
|
|
502
|
-
if (
|
|
503
|
-
|
|
504
|
-
|
|
501
|
+
if (this.handles?.length) {
|
|
502
|
+
// if (this.name === "Darbouka")
|
|
503
|
+
// console.log(this.name, this.gameObject.matrixWorldNeedsUpdate);
|
|
504
|
+
const needsUpdate: boolean = this.gameObject[NEED_UPDATE_INSTANCE_KEY] === true;// || this.gameObject.matrixWorldNeedsUpdate;
|
|
505
|
+
if (needsUpdate) {
|
|
506
|
+
if(debugInstancing)
|
|
507
|
+
console.log("UPDATE INSTANCED MATRICES", this.context.time.frame);
|
|
508
|
+
this.gameObject[NEED_UPDATE_INSTANCE_KEY] = false;
|
|
505
509
|
const remove = false;// Math.random() < .01;
|
|
506
510
|
for (let i = this.handles.length - 1; i >= 0; i--) {
|
|
507
511
|
const h = this.handles[i];
|
|
@@ -539,14 +543,7 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
539
543
|
|
|
540
544
|
onBeforeRenderThree(_renderer, _scene, _camera, _geometry, material, _group) {
|
|
541
545
|
|
|
542
|
-
|
|
543
|
-
if (!suppressProgressiveLoading && material._didRequestTextureLOD === undefined && this.allowProgressiveLoading) {
|
|
544
|
-
material._didRequestTextureLOD = 0;
|
|
545
|
-
if (debugProgressiveLoading) {
|
|
546
|
-
console.log("Load material LOD", material.name);
|
|
547
|
-
}
|
|
548
|
-
NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId, material);
|
|
549
|
-
}
|
|
546
|
+
this.loadProgressiveTextures(material);
|
|
550
547
|
|
|
551
548
|
if (material.envMapIntensity !== undefined) {
|
|
552
549
|
const factor = this.hasLightmap ? Math.PI : 1;
|
|
@@ -607,6 +604,22 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
607
604
|
}
|
|
608
605
|
}
|
|
609
606
|
|
|
607
|
+
loadProgressiveTextures(material: THREE.Material) {
|
|
608
|
+
// progressive load before rendering so we only load textures for visible materials
|
|
609
|
+
if (!suppressProgressiveLoading && material) {
|
|
610
|
+
if (debugProgressiveLoading && material["_didRequestTextureLOD"] === undefined)
|
|
611
|
+
console.warn("Progressive?", this)
|
|
612
|
+
|
|
613
|
+
if (material["_didRequestTextureLOD"] === undefined && this.allowProgressiveLoading) {
|
|
614
|
+
material["_didRequestTextureLOD"] = 0;
|
|
615
|
+
if (debugProgressiveLoading) {
|
|
616
|
+
console.log("Load material LOD", material.name);
|
|
617
|
+
}
|
|
618
|
+
NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId, material);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
610
623
|
private applySettings(go: THREE.Object3D) {
|
|
611
624
|
go.receiveShadow = this.receiveShadows;
|
|
612
625
|
if (this.shadowCastingMode == ShadowCastingMode.On) {
|
|
@@ -689,17 +702,22 @@ export enum ShadowCastingMode {
|
|
|
689
702
|
|
|
690
703
|
|
|
691
704
|
|
|
692
|
-
declare class InstancingSetupArgs {
|
|
705
|
+
declare class InstancingSetupArgs {
|
|
706
|
+
rend: Renderer;
|
|
707
|
+
foundMeshes: number;
|
|
708
|
+
useMatrixWorldAutoUpdate: boolean;
|
|
709
|
+
};
|
|
693
710
|
|
|
694
711
|
class InstancingHandler {
|
|
695
712
|
|
|
696
713
|
public objs: InstancedMeshRenderer[] = [];
|
|
697
714
|
|
|
698
|
-
public setup(obj: THREE.Object3D, context: Context, handlesArray: InstanceHandle[] | null, args: InstancingSetupArgs, level: number = 0)
|
|
715
|
+
public setup(renderer:Renderer, obj: THREE.Object3D, context: Context, handlesArray: InstanceHandle[] | null, args: InstancingSetupArgs, level: number = 0)
|
|
699
716
|
: InstanceHandle[] | null {
|
|
700
717
|
|
|
701
718
|
const res = this.tryCreateOrAddInstance(obj, context, args);
|
|
702
719
|
if (res) {
|
|
720
|
+
renderer.loadProgressiveTextures(res.instancer.material);
|
|
703
721
|
if (handlesArray === null) handlesArray = [];
|
|
704
722
|
handlesArray.push(res);
|
|
705
723
|
return handlesArray;
|
|
@@ -708,7 +726,7 @@ class InstancingHandler {
|
|
|
708
726
|
if (level <= 0 && obj.type !== "Mesh") {
|
|
709
727
|
const nextLevel = level + 1;
|
|
710
728
|
for (const ch of obj.children) {
|
|
711
|
-
handlesArray = this.setup(ch, context, handlesArray, args, nextLevel);
|
|
729
|
+
handlesArray = this.setup(renderer, ch, context, handlesArray, args, nextLevel);
|
|
712
730
|
}
|
|
713
731
|
}
|
|
714
732
|
return handlesArray;
|
|
@@ -736,16 +754,52 @@ class InstancingHandler {
|
|
|
736
754
|
for (const i of this.objs) {
|
|
737
755
|
if (i.isFull()) continue;
|
|
738
756
|
if (i.geo === geo && i.material === mat) {
|
|
739
|
-
|
|
757
|
+
const handle = i.addInstance(mesh);
|
|
758
|
+
if (args.useMatrixWorldAutoUpdate && handle)
|
|
759
|
+
this.autoUpdateInstanceMatrix(mesh, i, handle);
|
|
760
|
+
return handle;
|
|
740
761
|
}
|
|
741
762
|
}
|
|
742
763
|
// console.log("Add new instance mesh renderer", obj);
|
|
743
764
|
const i = new InstancedMeshRenderer(obj.name, geo, mat, 200, context);
|
|
744
765
|
this.objs.push(i);
|
|
745
|
-
|
|
766
|
+
const handle = i.addInstance(mesh);
|
|
767
|
+
if (args.useMatrixWorldAutoUpdate && handle)
|
|
768
|
+
this.autoUpdateInstanceMatrix(mesh, i, handle);
|
|
769
|
+
return handle;
|
|
746
770
|
}
|
|
747
771
|
return null;
|
|
748
772
|
}
|
|
773
|
+
|
|
774
|
+
private autoUpdateInstanceMatrix(obj: Object3D, _renderer: InstancedMeshRenderer, _handle: InstanceHandle) {
|
|
775
|
+
const original = obj.matrixWorld["multiplyMatrices"].bind(obj.matrixWorld);
|
|
776
|
+
let previousMatrix: THREE.Matrix4 = obj.matrixWorld.clone();
|
|
777
|
+
|
|
778
|
+
const matrixChangeWrapper = (a: Matrix4, b: Matrix4) => {
|
|
779
|
+
const newMatrixWorld = original(a, b);
|
|
780
|
+
// console.warn("MULT", obj.matrixWorldNeedsUpdate);
|
|
781
|
+
if (obj[NEED_UPDATE_INSTANCE_KEY] || previousMatrix.equals(newMatrixWorld) === false) {
|
|
782
|
+
previousMatrix.copy(newMatrixWorld)
|
|
783
|
+
// handle.setMatrix(newMatrixWorld);
|
|
784
|
+
obj[NEED_UPDATE_INSTANCE_KEY] = true;
|
|
785
|
+
}
|
|
786
|
+
return newMatrixWorld;
|
|
787
|
+
};
|
|
788
|
+
obj.matrixWorld["multiplyMatrices"] = matrixChangeWrapper;
|
|
789
|
+
|
|
790
|
+
// wrap matrixWorldNeedsUpdate
|
|
791
|
+
// let originalMatrixWorldNeedsUpdate = obj.matrixWorldNeedsUpdate;
|
|
792
|
+
// Object.defineProperty(obj, "matrixWorldNeedsUpdate", {
|
|
793
|
+
// get: () => {
|
|
794
|
+
// return originalMatrixWorldNeedsUpdate;
|
|
795
|
+
// },
|
|
796
|
+
// set: (value: boolean) => {
|
|
797
|
+
// if(value) console.warn("SET MATRIX WORLD NEEDS UPDATE");
|
|
798
|
+
// originalMatrixWorldNeedsUpdate = value;
|
|
799
|
+
// }
|
|
800
|
+
// });
|
|
801
|
+
|
|
802
|
+
}
|
|
749
803
|
}
|
|
750
804
|
const instancing: InstancingHandler = new InstancingHandler();
|
|
751
805
|
|
|
@@ -773,6 +827,11 @@ class InstanceHandle {
|
|
|
773
827
|
this.instancer.updateInstance(this.object.matrixWorld, this.instanceIndex);
|
|
774
828
|
}
|
|
775
829
|
|
|
830
|
+
setMatrix(matrix: THREE.Matrix4) {
|
|
831
|
+
if (this.instanceIndex < 0) return;
|
|
832
|
+
this.instancer.updateInstance(matrix, this.instanceIndex);
|
|
833
|
+
}
|
|
834
|
+
|
|
776
835
|
add() {
|
|
777
836
|
if (this.instanceIndex >= 0) return;
|
|
778
837
|
this.instancer.add(this);
|
|
@@ -12,7 +12,6 @@ export function apply(object: Object3D) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
Object3D.prototype["SetActive"] = function (active: boolean) {
|
|
17
16
|
this.visible = active;
|
|
18
17
|
}
|
|
@@ -69,6 +68,13 @@ if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "activeSelf")) {
|
|
|
69
68
|
});
|
|
70
69
|
}
|
|
71
70
|
|
|
71
|
+
if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "transform")) {
|
|
72
|
+
Object.defineProperty(Object3D.prototype, "transform", {
|
|
73
|
+
get: function () {
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
72
78
|
|
|
73
79
|
|
|
74
80
|
|
|
@@ -84,9 +84,13 @@ export class PlayableDirector extends Behaviour {
|
|
|
84
84
|
set duration(value: number) { this._duration = value; }
|
|
85
85
|
get weight(): number { return this._weight; };
|
|
86
86
|
set weight(value: number) { this._weight = value; }
|
|
87
|
+
get speed(): number { return this._speed; }
|
|
88
|
+
set speed(value: number) { this._speed = value; }
|
|
89
|
+
|
|
87
90
|
|
|
88
91
|
private _visibilityChangeEvt?: any;
|
|
89
92
|
private _clonedPlayableAsset: boolean = false;
|
|
93
|
+
private _speed: number = 1;
|
|
90
94
|
|
|
91
95
|
awake(): void {
|
|
92
96
|
if (debug)
|
|
@@ -149,7 +153,9 @@ export class PlayableDirector extends Behaviour {
|
|
|
149
153
|
|
|
150
154
|
play() {
|
|
151
155
|
if (!this.isValid()) return;
|
|
156
|
+
const pauseChanged = this._isPaused == true;
|
|
152
157
|
this._isPaused = false;
|
|
158
|
+
if (pauseChanged) this.invokePauseChangedMethodsOnTracks();
|
|
153
159
|
if (this._isPlaying) return;
|
|
154
160
|
this._isPlaying = true;
|
|
155
161
|
this._internalUpdateRoutine = this.startCoroutine(this.internalUpdate());
|
|
@@ -161,18 +167,23 @@ export class PlayableDirector extends Behaviour {
|
|
|
161
167
|
if (this._isPaused) return;
|
|
162
168
|
this._isPaused = true;
|
|
163
169
|
this.evaluate();
|
|
170
|
+
this.invokePauseChangedMethodsOnTracks();
|
|
164
171
|
}
|
|
165
172
|
|
|
166
173
|
stop() {
|
|
167
|
-
for(const track of this._audioTracks) track.stop();
|
|
174
|
+
for (const track of this._audioTracks) track.stop();
|
|
175
|
+
const pauseChanged = this._isPaused == true;
|
|
176
|
+
const wasPlaying = this._isPlaying;
|
|
168
177
|
if (this._isPlaying) {
|
|
169
178
|
this._time = 0;
|
|
170
179
|
this._isPlaying = false;
|
|
171
180
|
this._isPaused = false;
|
|
172
181
|
this.evaluate();
|
|
182
|
+
if (pauseChanged) this.invokePauseChangedMethodsOnTracks();
|
|
173
183
|
}
|
|
174
184
|
this._isPlaying = false;
|
|
175
185
|
this._isPaused = false;
|
|
186
|
+
if (pauseChanged && !wasPlaying) this.invokePauseChangedMethodsOnTracks();
|
|
176
187
|
if (this._internalUpdateRoutine)
|
|
177
188
|
this.stopCoroutine(this._internalUpdateRoutine);
|
|
178
189
|
this._internalUpdateRoutine = null;
|
|
@@ -183,10 +194,15 @@ export class PlayableDirector extends Behaviour {
|
|
|
183
194
|
let t = this._time;
|
|
184
195
|
switch (this.extrapolationMode) {
|
|
185
196
|
case DirectorWrapMode.Hold:
|
|
186
|
-
|
|
197
|
+
if(this._speed > 0)
|
|
198
|
+
t = Math.min(t, this._duration);
|
|
199
|
+
else if(this._speed < 0)
|
|
200
|
+
t = Math.max(t, 0);
|
|
201
|
+
this._time = t;
|
|
187
202
|
break;
|
|
188
203
|
case DirectorWrapMode.Loop:
|
|
189
204
|
t %= this._duration;
|
|
205
|
+
this._time = t;
|
|
190
206
|
break;
|
|
191
207
|
case DirectorWrapMode.None:
|
|
192
208
|
if (t > this._duration) {
|
|
@@ -240,10 +256,17 @@ export class PlayableDirector extends Behaviour {
|
|
|
240
256
|
this._customTracks
|
|
241
257
|
];
|
|
242
258
|
|
|
259
|
+
/** should be called after evaluate if the director was playing */
|
|
260
|
+
private invokePauseChangedMethodsOnTracks() {
|
|
261
|
+
for (const track of this.forEachTrack()) {
|
|
262
|
+
track.onPauseChanged?.call(track);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
243
266
|
private *internalUpdate() {
|
|
244
267
|
while (this._isPlaying && this.activeAndEnabled) {
|
|
245
268
|
if (!this._isPaused && this._isPlaying) {
|
|
246
|
-
this._time += this.context.time.deltaTime;
|
|
269
|
+
this._time += this.context.time.deltaTime * this.speed;
|
|
247
270
|
this.evaluate();
|
|
248
271
|
}
|
|
249
272
|
// for (let i = 0; i < 5; i++)
|
|
@@ -278,10 +301,8 @@ export class PlayableDirector extends Behaviour {
|
|
|
278
301
|
for (const handler of this._animationTracks) {
|
|
279
302
|
handler.evaluate(time);
|
|
280
303
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
handler.evaluate(time);
|
|
284
|
-
}
|
|
304
|
+
for (const handler of this._audioTracks) {
|
|
305
|
+
handler.evaluate(time);
|
|
285
306
|
}
|
|
286
307
|
for (const sig of this._signalTracks) {
|
|
287
308
|
sig.evaluate(time);
|
|
@@ -369,8 +390,7 @@ export class PlayableDirector extends Behaviour {
|
|
|
369
390
|
this._signalTracks.length = 0;
|
|
370
391
|
|
|
371
392
|
if (!this.playableAsset) return;
|
|
372
|
-
const
|
|
373
|
-
const audioAllowedCallbacks : any = [];
|
|
393
|
+
const audioListener: AudioListener | null = GameObject.findObjectOfType(AudioListener, this.context);
|
|
374
394
|
for (const track of this.playableAsset!.tracks) {
|
|
375
395
|
const type = track.type;
|
|
376
396
|
const registered = PlayableDirector.createTrackFunctions[type];
|
|
@@ -442,16 +462,12 @@ export class PlayableDirector extends Behaviour {
|
|
|
442
462
|
audio.director = this;
|
|
443
463
|
audio.track = track;
|
|
444
464
|
this._audioTracks.push(audio);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
audio.
|
|
450
|
-
|
|
451
|
-
const clipModel = track.clips[i];
|
|
452
|
-
audio.addModel(clipModel);
|
|
453
|
-
}
|
|
454
|
-
});
|
|
465
|
+
if (!audioListener) continue;
|
|
466
|
+
audio.listener = audioListener.listener;
|
|
467
|
+
for (let i = 0; i < track.clips.length; i++) {
|
|
468
|
+
const clipModel = track.clips[i];
|
|
469
|
+
audio.addModel(clipModel);
|
|
470
|
+
}
|
|
455
471
|
}
|
|
456
472
|
else if (track.type === Models.TrackType.Marker) {
|
|
457
473
|
const signalHandler: Tracks.SignalTrackHandler = new Tracks.SignalTrackHandler();
|
|
@@ -497,11 +513,6 @@ export class PlayableDirector extends Behaviour {
|
|
|
497
513
|
this._controlTracks.push(handler);
|
|
498
514
|
}
|
|
499
515
|
}
|
|
500
|
-
|
|
501
|
-
AudioSource.registerWaitForAllowAudio(() => {
|
|
502
|
-
audioAllowedCallbacks.forEach((cb: any) => cb());
|
|
503
|
-
audioAllowedCallbacks.length = 0;
|
|
504
|
-
});
|
|
505
516
|
}
|
|
506
517
|
|
|
507
518
|
private setAudioTracksAllowPlaying(allow: boolean) {
|
|
@@ -6,6 +6,8 @@ import { Context } from "../../engine/engine_setup";
|
|
|
6
6
|
import { SignalReceiver } from "./SignalAsset";
|
|
7
7
|
import { AnimationClip, Quaternion, Vector3 } from "three";
|
|
8
8
|
import { getParam, getPath } from "../../engine/engine_utils";
|
|
9
|
+
import { AudioSource } from "../AudioSource";
|
|
10
|
+
import { Animator } from "../Animator"
|
|
9
11
|
|
|
10
12
|
const debug = getParam("debugtimeline");
|
|
11
13
|
|
|
@@ -40,6 +42,7 @@ export abstract class TrackHandler {
|
|
|
40
42
|
onDestroy?();
|
|
41
43
|
abstract evaluate(time: number);
|
|
42
44
|
onMuteChanged?();
|
|
45
|
+
onPauseChanged?();
|
|
43
46
|
|
|
44
47
|
getClipTime(time: number, model: Models.ClipModel) {
|
|
45
48
|
return model.clipIn + (time - model.start) * model.timeScale;
|
|
@@ -136,6 +139,16 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
136
139
|
/** holds data/info about clips differences */
|
|
137
140
|
private _actionOffsets: Array<AnimationClipOffsetData> = [];
|
|
138
141
|
private _didBind: boolean = false;
|
|
142
|
+
private _animator: Animator | null = null;
|
|
143
|
+
private _animatorWasEnabled?: boolean = false;
|
|
144
|
+
|
|
145
|
+
onPauseChanged() {
|
|
146
|
+
// When the timeline is paused the original animator will be enabled again if it was before
|
|
147
|
+
if (this._animator && this._animatorWasEnabled !== undefined) {
|
|
148
|
+
this._animator.enabled = this.director.isPaused ? this._animatorWasEnabled : false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
139
152
|
|
|
140
153
|
createHooks(clipModel: Models.AnimationClipModel, clip) {
|
|
141
154
|
if (clip.tracks?.length <= 0) {
|
|
@@ -208,6 +221,13 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
208
221
|
this._actionOffsets.push(off);
|
|
209
222
|
}
|
|
210
223
|
|
|
224
|
+
if (this.target) {
|
|
225
|
+
// We need to disable the animator component in case it also animates
|
|
226
|
+
// which overrides the timeline
|
|
227
|
+
this._animator = GameObject.getComponent(this.target, Animator) ?? null;
|
|
228
|
+
this._animatorWasEnabled = this._animator?.enabled;
|
|
229
|
+
}
|
|
230
|
+
|
|
211
231
|
// Clip Offsets
|
|
212
232
|
for (const model of this.models) {
|
|
213
233
|
const clipData = model.asset as Models.AnimationClipModel;
|
|
@@ -261,6 +281,8 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
261
281
|
if (!this.mixer) return;
|
|
262
282
|
this.bind();
|
|
263
283
|
|
|
284
|
+
if (this._animator && this.director.isPlaying && this.director.weight > 0) this._animator.enabled = false;
|
|
285
|
+
|
|
264
286
|
this._totalOffsetPosition.set(0, 0, 0);
|
|
265
287
|
this._totalOffsetRotation.set(0, 0, 0, 1);
|
|
266
288
|
this._totalOffsetPosition2.set(0, 0, 0);
|
|
@@ -268,38 +290,60 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
268
290
|
let activeClips = 0;
|
|
269
291
|
let blend: number = 0;
|
|
270
292
|
let didPostExtrapolate = false;
|
|
293
|
+
let didPreExtrapolate = false;
|
|
271
294
|
for (let i = 0; i < this.clips.length; i++) {
|
|
272
295
|
const model = this.models[i];
|
|
273
296
|
const action = this.actions[i];
|
|
274
297
|
const clipModel = model.asset as Models.AnimationClipModel;
|
|
275
298
|
action.weight = 0;
|
|
299
|
+
|
|
276
300
|
const isInTimeRange = time >= model.start && time <= model.end;
|
|
301
|
+
const preExtrapolation: Models.ClipExtrapolation = model.preExtrapolationMode;
|
|
277
302
|
const postExtrapolation: Models.ClipExtrapolation = model.postExtrapolationMode;
|
|
303
|
+
const nextClip = i < this.clips.length - 1 ? this.models[i + 1] : null;
|
|
278
304
|
let isActive = isInTimeRange;
|
|
305
|
+
let doPreExtrapolate = false;
|
|
306
|
+
|
|
279
307
|
if (!isActive && !didPostExtrapolate && model.end < time && postExtrapolation !== Models.ClipExtrapolation.None) {
|
|
280
|
-
const nextClip = i < this.clips.length - 1 ? this.models[i + 1] : null;
|
|
281
308
|
// use post extrapolate if its the last clip of the next clip has not yet started
|
|
282
309
|
if (!nextClip || nextClip.start > time) {
|
|
283
310
|
isActive = true;
|
|
284
311
|
didPostExtrapolate = true;
|
|
285
312
|
}
|
|
286
313
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
314
|
+
else if (i == 0 && !isActive && !didPreExtrapolate && model.start > time && preExtrapolation !== Models.ClipExtrapolation.None) {
|
|
315
|
+
if (!nextClip || nextClip.start < time) {
|
|
316
|
+
isActive = true;
|
|
317
|
+
doPreExtrapolate = true;
|
|
318
|
+
didPreExtrapolate = true;
|
|
319
|
+
}
|
|
292
320
|
}
|
|
321
|
+
|
|
293
322
|
if (isActive) {
|
|
294
323
|
// const clip = this.clips[i];
|
|
295
324
|
let weight = 1;
|
|
296
325
|
weight *= this.evaluateWeight(time, i, this.models, isActive);
|
|
326
|
+
|
|
327
|
+
let handleLoop = isInTimeRange;
|
|
328
|
+
if(doPreExtrapolate){
|
|
329
|
+
if (preExtrapolation !== Models.ClipExtrapolation.Hold) {
|
|
330
|
+
time += model.start;
|
|
331
|
+
handleLoop = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
297
335
|
// TODO: handle clipIn again
|
|
298
336
|
let t = this.getClipTime(time, model);
|
|
299
337
|
let loops = 0;
|
|
300
338
|
const duration = clipModel.duration;
|
|
301
339
|
|
|
302
|
-
if (
|
|
340
|
+
if (doPreExtrapolate) {
|
|
341
|
+
if (preExtrapolation === Models.ClipExtrapolation.Hold) {
|
|
342
|
+
t = 0;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (handleLoop) {
|
|
303
347
|
if (clipModel.loop) {
|
|
304
348
|
// const t0 = t - .001;
|
|
305
349
|
loops += Math.floor(t / (duration + .000001));
|
|
@@ -468,6 +512,8 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
468
512
|
audioContextTimeOffset: Array<number> = [];
|
|
469
513
|
lastTime: number = 0;
|
|
470
514
|
|
|
515
|
+
private _audioLoader: THREE.AudioLoader | null = null;
|
|
516
|
+
|
|
471
517
|
private getAudioFilePath(path: string) {
|
|
472
518
|
// TODO: this should be the timeline asset location probably which MIGHT be different
|
|
473
519
|
const glbLocation = this.director.sourceId;
|
|
@@ -483,18 +529,9 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
483
529
|
}
|
|
484
530
|
|
|
485
531
|
addModel(model: Models.ClipModel) {
|
|
486
|
-
const path = this.getAudioFilePath(model.asset.clip);
|
|
487
532
|
const audio = new THREE.Audio(this.listener);
|
|
488
|
-
audio.
|
|
489
|
-
|
|
490
|
-
if (debug)
|
|
491
|
-
console.log(path, this.director.sourceId);
|
|
492
|
-
loader.load(path, (buffer) => {
|
|
493
|
-
audio.setBuffer(buffer);
|
|
494
|
-
audio.loop = model.asset.loop;
|
|
495
|
-
this.audio.push(audio);
|
|
496
|
-
this.models.push(model);
|
|
497
|
-
});
|
|
533
|
+
this.audio.push(audio);
|
|
534
|
+
this.models.push(model);
|
|
498
535
|
}
|
|
499
536
|
|
|
500
537
|
onDisable() {
|
|
@@ -521,9 +558,14 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
521
558
|
audio.stop();
|
|
522
559
|
}
|
|
523
560
|
}
|
|
561
|
+
|
|
524
562
|
evaluate(time: number) {
|
|
525
563
|
if (muteAudioTracks) return;
|
|
526
564
|
if (this.track.muted) return;
|
|
565
|
+
if (this.director.speed < 0) {
|
|
566
|
+
// Reversed audio playback is currently not supported
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
527
569
|
const isMuted = this.director.context.application.muted;
|
|
528
570
|
// this is just so that we dont hear the very first beat when the audio starts but is muted
|
|
529
571
|
// if we dont add a delay we hear a little bit of the audio before it shuts down
|
|
@@ -532,7 +574,15 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
532
574
|
for (let i = 0; i < this.models.length; i++) {
|
|
533
575
|
const model = this.models[i];
|
|
534
576
|
const audio = this.audio[i];
|
|
535
|
-
|
|
577
|
+
// only trigger loading for tracks that are CLOSE to being played
|
|
578
|
+
if ((!audio || !audio.buffer) && this.isInTimeRange(model, time - 1, time + 1)) {
|
|
579
|
+
this.handleAudioLoading(model, audio);
|
|
580
|
+
}
|
|
581
|
+
if (AudioSource.userInteractionRegistered === false) continue;
|
|
582
|
+
if (audio === null || !audio.buffer) continue;
|
|
583
|
+
audio.playbackRate = this.director.context.time.timeScale;
|
|
584
|
+
audio.loop = model.asset.loop;
|
|
585
|
+
if (time >= model.start && time <= model.end && time < this.director.duration) {
|
|
536
586
|
if (this.director.isPlaying == false) {
|
|
537
587
|
if (audio.isPlaying)
|
|
538
588
|
audio.stop();
|
|
@@ -556,7 +606,7 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
556
606
|
}
|
|
557
607
|
}
|
|
558
608
|
let vol = model.asset.volume as number;
|
|
559
|
-
if(isMuted) vol = 0;
|
|
609
|
+
if (isMuted) vol = 0;
|
|
560
610
|
if (model.easeInDuration > 0) {
|
|
561
611
|
const easeIn = Math.min((time - model.start) / model.easeInDuration, 1);
|
|
562
612
|
vol *= easeIn;
|
|
@@ -574,6 +624,60 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
574
624
|
}
|
|
575
625
|
this.lastTime = time;
|
|
576
626
|
}
|
|
627
|
+
|
|
628
|
+
/** Call to load audio buffer for a specific time in the track. Can be used to preload the timeline audio */
|
|
629
|
+
loadAudio(time: number, lookAhead: number = 0, lookBehind: number = 0) {
|
|
630
|
+
let promises: Array<Promise<AudioBuffer | null>> | null = null;
|
|
631
|
+
const rangeStart = time - lookBehind;
|
|
632
|
+
const rangeEnd = time + lookAhead;
|
|
633
|
+
for (const model of this.models) {
|
|
634
|
+
if (this.isInTimeRange(model, rangeStart, rangeEnd)) {
|
|
635
|
+
const audio = this.audio[this.models.indexOf(model)];
|
|
636
|
+
const promise = this.handleAudioLoading(model, audio);
|
|
637
|
+
if (promise !== null) {
|
|
638
|
+
if (promises === null) promises = [];
|
|
639
|
+
promises.push(promise);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (promises !== null) {
|
|
644
|
+
return Promise.all(promises);
|
|
645
|
+
}
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private isInTimeRange(model: Models.ClipModel, start: number, end: number) {
|
|
650
|
+
// Range surrounds clip range
|
|
651
|
+
if (start <= model.start && end >= model.end) return true;
|
|
652
|
+
// Range start is in clip range
|
|
653
|
+
if (start >= model.start && start <= model.end) return true;
|
|
654
|
+
// Range end is in clip range
|
|
655
|
+
if (end >= model.start && end <= model.end) return true;
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
private handleAudioLoading(model: Models.ClipModel, audio: THREE.Audio): Promise<AudioBuffer | null> | null {
|
|
660
|
+
if (!this._audioLoader) {
|
|
661
|
+
const path = this.getAudioFilePath(model.asset.clip);
|
|
662
|
+
this._audioLoader = new THREE.AudioLoader();
|
|
663
|
+
// TODO: maybe we should cache the loaders / buffers here by path
|
|
664
|
+
if (debug) console.warn("LOAD audio track", path, this.director.sourceId);
|
|
665
|
+
const loadingPromise = new Promise<AudioBuffer | null>((resolve, _reject) => {
|
|
666
|
+
this._audioLoader!.load(path,
|
|
667
|
+
buffer => {
|
|
668
|
+
audio.setBuffer(buffer);
|
|
669
|
+
resolve(buffer);
|
|
670
|
+
},
|
|
671
|
+
undefined,
|
|
672
|
+
err => {
|
|
673
|
+
console.error("Error loading audio", err);
|
|
674
|
+
resolve(null);
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
return loadingPromise;
|
|
678
|
+
}
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
577
681
|
}
|
|
578
682
|
|
|
579
683
|
export class SignalTrackHandler extends TrackHandler {
|