@@ -5,7 +5,33 @@
|
|
5
5
|
import { Animation } from "./Animation";
|
6
6
|
import { GameObject } from "./Component";
|
7
7
|
import { PlayableDirector } from "./timeline/PlayableDirector";
|
8
|
+
import { Object3D } from "three";
|
8
9
|
|
10
|
+
|
11
|
+
const $objectAnimationKey = Symbol("objectIsAnimatedData");
|
12
|
+
|
13
|
+
/** Internal method - This marks an object as being animated. Make sure to always call isAnimated=false if you stop animating the object
|
14
|
+
* @param obj The object to mark
|
15
|
+
* @param isAnimated Whether the object is animated or not
|
16
|
+
*/
|
17
|
+
export function setObjectAnimated(obj: Object3D, isAnimated: boolean) {
|
18
|
+
if (obj[$objectAnimationKey] === undefined) {
|
19
|
+
if (!isAnimated) return;
|
20
|
+
obj[$objectAnimationKey] = 1;
|
21
|
+
}
|
22
|
+
else {
|
23
|
+
if (isAnimated)
|
24
|
+
obj[$objectAnimationKey] += 1;
|
25
|
+
else if (obj[$objectAnimationKey] > 0)
|
26
|
+
obj[$objectAnimationKey] -= 1;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
/** Get is the object is currently animated. Currently used by the Animator to check if a timeline animationtrack is actively animating an object */
|
31
|
+
export function getObjectAnimated(obj: Object3D): boolean {
|
32
|
+
return obj[$objectAnimationKey] !== undefined && obj[$objectAnimationKey] > 0;
|
33
|
+
}
|
34
|
+
|
9
35
|
ContextRegistry.registerCallback(ContextEvent.ContextCreated, args => {
|
10
36
|
const autoplay = args.context.domElement.getAttribute("autoplay");
|
11
37
|
if (autoplay !== undefined && (autoplay === "" || autoplay === "true")) {
|
@@ -5,6 +5,7 @@
|
|
5
5
|
import { AnimatorController } from "./AnimatorController";
|
6
6
|
import { serializable } from "../engine/engine_serialization_decorator";
|
7
7
|
import { Mathf } from "../engine/engine_math";
|
8
|
+
import { getObjectAnimated } from "./AnimationUtils";
|
8
9
|
|
9
10
|
const debug = getParam("debuganimator");
|
10
11
|
|
@@ -102,7 +103,7 @@
|
|
102
103
|
/**@deprecated use setTrigger */
|
103
104
|
SetTrigger(name: string | number) { this.setTrigger(name); }
|
104
105
|
setTrigger(name: string | number) {
|
105
|
-
if(debug) console.log("SetTrigger", name);
|
106
|
+
if (debug) console.log("SetTrigger", name);
|
106
107
|
this.runtimeAnimatorController?.setTrigger(name);
|
107
108
|
}
|
108
109
|
|
@@ -168,6 +169,8 @@
|
|
168
169
|
}
|
169
170
|
|
170
171
|
onBeforeRender() {
|
172
|
+
if (getObjectAnimated(this.gameObject)) return;
|
173
|
+
|
171
174
|
if (this._animatorController) {
|
172
175
|
this._animatorController.update();
|
173
176
|
}
|
@@ -24,7 +24,7 @@
|
|
24
24
|
import { isDevEnvironment } from './debug/debug';
|
25
25
|
|
26
26
|
const debugPhysics = getParam("debugphysics");
|
27
|
-
const debugColliderPlacement = getParam("
|
27
|
+
const debugColliderPlacement = getParam("debugcolliderplacement");
|
28
28
|
const debugCollisions = getParam("debugcollisions");
|
29
29
|
const showColliders = getParam("showcolliders");
|
30
30
|
|
@@ -49,6 +49,13 @@
|
|
49
49
|
}
|
50
50
|
|
51
51
|
private _timevec4: Vector4 = new Vector4();
|
52
|
+
|
53
|
+
/** Time data used for custom shaders
|
54
|
+
* x: time
|
55
|
+
* y: sin(time)
|
56
|
+
* z: cos(time)
|
57
|
+
* w: deltaTime
|
58
|
+
*/
|
52
59
|
get timeVec4(): Vector4 {
|
53
60
|
return this._timevec4;
|
54
61
|
}
|
@@ -74,6 +74,11 @@
|
|
74
74
|
onDeserialize(data: ObjectData | string | null, context: SerializationContext) {
|
75
75
|
|
76
76
|
if (typeof data === "string") {
|
77
|
+
if (data.endsWith(".glb") || data.endsWith(".gltf")) {
|
78
|
+
if (isDevEnvironment())
|
79
|
+
showBalloonWarning("Detected wrong usage of @serializable with Object3D or GameObject. Instead you should use AssetReference here! Please see the console for details.");
|
80
|
+
console.warn("Wrong usage of @serializable detected:\n\nIt looks like you used @serializable(Object3D) or @serializable(GameObject) for a prefab or scene reference named \"" + context.path + "\" in your script \"" + context.target?.constructor?.name + "\". \nTo fix this replace it with @serializable(AssetReference). Make sure to replace the type with AssetReference too.\n\nIt should then look something like this:\n\n@serializable(AssetReference)\n" + context.path + "! : AssetReference;\n\0");
|
81
|
+
}
|
77
82
|
// ACTUALLY: this is already handled by the extension_utils where we resolve json pointers recursively
|
78
83
|
// if(data.startsWith("/nodes/")){
|
79
84
|
// const node = parseInt(data.substring("/nodes/".length));
|
@@ -212,9 +212,34 @@
|
|
212
212
|
if (this.uniforms["_TimeParameters"]) {
|
213
213
|
this.uniforms["_TimeParameters"].value = context.sceneLighting.timeVec4;
|
214
214
|
}
|
215
|
-
|
216
|
-
this.uniforms["_Time"].value
|
215
|
+
if (this.uniforms["_Time"]) {
|
216
|
+
const _time = this.uniforms["_Time"].value as Vector4;
|
217
|
+
_time.x = context.sceneLighting.timeVec4.x / 20;
|
218
|
+
_time.y = context.sceneLighting.timeVec4.x;
|
219
|
+
_time.z = context.sceneLighting.timeVec4.x * 2;
|
220
|
+
_time.w = context.sceneLighting.timeVec4.x * 3;
|
217
221
|
}
|
222
|
+
if (this.uniforms["_SinTime"]) {
|
223
|
+
const _time = this.uniforms["_SinTime"].value as Vector4;
|
224
|
+
_time.x = Math.sin(context.sceneLighting.timeVec4.x / 8);
|
225
|
+
_time.y = Math.sin(context.sceneLighting.timeVec4.x / 4);
|
226
|
+
_time.z = Math.sin(context.sceneLighting.timeVec4.x / 2);
|
227
|
+
_time.w = Math.sin(context.sceneLighting.timeVec4.x);
|
228
|
+
}
|
229
|
+
if (this.uniforms["_CosTime"]) {
|
230
|
+
const _time = this.uniforms["_CosTime"].value as Vector4;
|
231
|
+
_time.x = Math.cos(context.sceneLighting.timeVec4.x / 8);
|
232
|
+
_time.y = Math.cos(context.sceneLighting.timeVec4.x / 4);
|
233
|
+
_time.z = Math.cos(context.sceneLighting.timeVec4.x / 2);
|
234
|
+
_time.w = Math.cos(context.sceneLighting.timeVec4.x);
|
235
|
+
}
|
236
|
+
if (this.uniforms["unity_DeltaTime"]) {
|
237
|
+
const _time = this.uniforms["unity_DeltaTime"].value as Vector4;
|
238
|
+
_time.x = context.time.deltaTime;
|
239
|
+
_time.y = 1 / context.time.deltaTime;
|
240
|
+
_time.z = context.time.smoothedDeltaTime;
|
241
|
+
_time.w = 1 / context.time.smoothedDeltaTime;
|
242
|
+
}
|
218
243
|
|
219
244
|
const mainLight: ILight | null = context.mainLight;
|
220
245
|
if (mainLight) {
|
@@ -340,9 +365,17 @@
|
|
340
365
|
const uniforms: {} = {};
|
341
366
|
const techniqueUniforms = technique.uniforms;
|
342
367
|
|
343
|
-
|
368
|
+
// BiRP time uniforms
|
369
|
+
if (vert.includes("_Time") || frag.includes("_Time"))
|
344
370
|
uniforms["_Time"] = { value: new Vector4(0, 0, 0, 0) };
|
371
|
+
if (vert.includes("_SinTime") || frag.includes("_SinTime"))
|
372
|
+
uniforms["_SinTime"] = { value: new Vector4(0, 0, 0, 0) };
|
373
|
+
if (vert.includes("_CosTime") || frag.includes("_CosTime"))
|
374
|
+
uniforms["_CosTime"] = { value: new Vector4(0, 0, 0, 0) };
|
375
|
+
if (vert.includes("unity_DeltaTime") || frag.includes("unity_DeltaTime"))
|
376
|
+
uniforms["unity_DeltaTime"] = { value: new Vector4(0, 0, 0, 0) };
|
345
377
|
|
378
|
+
|
346
379
|
for (const u in techniqueUniforms) {
|
347
380
|
const uniformName = u;
|
348
381
|
// const uniformValues = techniqueUniforms[u];
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Behaviour, GameObject } from "./Component";
|
2
2
|
import * as THREE from "three";
|
3
|
-
import { MainModule, EmissionModule, ShapeModule, ParticleSystemShapeType, MinMaxCurve, MinMaxGradient, ColorOverLifetimeModule, SizeOverLifetimeModule, NoiseModule, ParticleSystemSimulationSpace, ParticleBurst, IParticleSystem, ParticleSystemRenderMode, TrailModule, VelocityOverLifetimeModule, TextureSheetAnimationModule, RotationOverLifetimeModule, LimitVelocityOverLifetimeModule, RotationBySpeedModule, InheritVelocityModule, SizeBySpeedModule, ColorBySpeedModule } from "./ParticleSystemModules"
|
3
|
+
import { MainModule, EmissionModule, ShapeModule, ParticleSystemShapeType, MinMaxCurve, MinMaxGradient, ColorOverLifetimeModule, SizeOverLifetimeModule, NoiseModule, ParticleSystemSimulationSpace, ParticleBurst, IParticleSystem, ParticleSystemRenderMode, TrailModule, VelocityOverLifetimeModule, TextureSheetAnimationModule, RotationOverLifetimeModule, LimitVelocityOverLifetimeModule, RotationBySpeedModule, InheritVelocityModule, SizeBySpeedModule, ColorBySpeedModule, ParticleSystemScalingMode } from "./ParticleSystemModules"
|
4
4
|
import { getParam } from "../engine/engine_utils";
|
5
5
|
|
6
6
|
// https://github.dev/creativelifeform/three-nebula
|
@@ -18,7 +18,7 @@
|
|
18
18
|
import { ParticleSubEmitter } from "./ParticleSystemSubEmitter";
|
19
19
|
import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive";
|
20
20
|
import { Gizmos } from "../engine/engine_gizmos";
|
21
|
-
import { isDevEnvironment } from "../engine/debug";
|
21
|
+
import { isDevEnvironment, showBalloonWarning } from "../engine/debug";
|
22
22
|
|
23
23
|
const debug = getParam("debugparticles");
|
24
24
|
const suppressProgressiveLoading = getParam("noprogressive");
|
@@ -327,6 +327,7 @@
|
|
327
327
|
}
|
328
328
|
|
329
329
|
const $sizeLerpFactor = Symbol("sizeLerpFactor");
|
330
|
+
const localScaleVec3 = new Vector3();
|
330
331
|
class SizeBehaviour extends ParticleSystemBaseBehaviour {
|
331
332
|
|
332
333
|
type: string = "NeedleSize";
|
@@ -347,6 +348,10 @@
|
|
347
348
|
size *= this.system.sizeOverLifetime.evaluate(age01, undefined, particle[$sizeLerpFactor]).x;
|
348
349
|
const scaleFactor = this.system.worldScale.x / this.system.cameraScale;
|
349
350
|
particle.size = particle.startSize * size * scaleFactor;
|
351
|
+
if (this.system.localspace) {
|
352
|
+
const scale = getLocalSimulationScale(this.system, localScaleVec3);
|
353
|
+
particle.size *= scale.x;
|
354
|
+
}
|
350
355
|
// in Unity this is viewport size, we don't really support this yet (and the renderer is logging a warning)
|
351
356
|
// so for now it's disabled again
|
352
357
|
// particle.size = Mathf.clamp(particle.size, this._minSize, this._maxSize);
|
@@ -770,6 +775,9 @@
|
|
770
775
|
get worldspace() {
|
771
776
|
return this.main.simulationSpace === ParticleSystemSimulationSpace.World;
|
772
777
|
}
|
778
|
+
get localspace() {
|
779
|
+
return this.main.simulationSpace === ParticleSystemSimulationSpace.Local;
|
780
|
+
}
|
773
781
|
|
774
782
|
private __worldQuaternion = new Quaternion();
|
775
783
|
get worldQuaternion(): Quaternion {
|
@@ -999,11 +1007,7 @@
|
|
999
1007
|
|
1000
1008
|
// Handle LOCALSPACE
|
1001
1009
|
if (isLocalSpace && this._container && this.gameObject?.parent) {
|
1002
|
-
|
1003
|
-
const scale = getWorldScale(this.gameObject.parent);
|
1004
|
-
scale.x = 1 / scale.x;
|
1005
|
-
scale.y = 1 / scale.y;
|
1006
|
-
scale.z = 1 / scale.z;
|
1010
|
+
const scale = getLocalSimulationScale(this, temp3);
|
1007
1011
|
this._container.matrix.makeScale(scale.x, scale.y, scale.z);
|
1008
1012
|
this._container.matrix.makeRotationFromQuaternion(this.__worldQuaternion);
|
1009
1013
|
this._container.matrix.setPosition(this.worldPos);
|
@@ -1063,4 +1067,30 @@
|
|
1063
1067
|
console.warn("Could not find particle system for sub emitter", guid, gameObject, this);
|
1064
1068
|
}
|
1065
1069
|
}
|
1070
|
+
}
|
1071
|
+
|
1072
|
+
|
1073
|
+
function getLocalSimulationScale(system: ParticleSystem, vec: Vector3) {
|
1074
|
+
vec.set(1, 1, 1);
|
1075
|
+
if (system.gameObject.parent && system.localspace) {
|
1076
|
+
switch (system.main.scalingMode) {
|
1077
|
+
case ParticleSystemScalingMode.Local:
|
1078
|
+
vec = getWorldScale(system.gameObject.parent, vec);
|
1079
|
+
vec.x = 1 / vec.x;
|
1080
|
+
vec.y = 1 / vec.y;
|
1081
|
+
vec.z = 1 / vec.z;
|
1082
|
+
break;
|
1083
|
+
default:
|
1084
|
+
if (!system["unsupported_scaling_mode"]) {
|
1085
|
+
system["unsupported_scaling_mode"] = true;
|
1086
|
+
const msg = "ParticleSystem scale mode " + ParticleSystemScalingMode[system.main.scalingMode] + " is not supported";
|
1087
|
+
if (isDevEnvironment())
|
1088
|
+
showBalloonWarning(msg);
|
1089
|
+
console.warn(msg, system.name, system);
|
1090
|
+
}
|
1091
|
+
vec = getWorldScale(system.gameObject, vec);
|
1092
|
+
break;
|
1093
|
+
}
|
1094
|
+
}
|
1095
|
+
return vec;
|
1066
1096
|
}
|
@@ -270,10 +270,10 @@
|
|
270
270
|
}
|
271
271
|
}
|
272
272
|
|
273
|
-
|
274
|
-
Hierarchy
|
275
|
-
Local
|
276
|
-
Shape
|
273
|
+
export enum ParticleSystemScalingMode {
|
274
|
+
Hierarchy = 0,
|
275
|
+
Local = 1,
|
276
|
+
Shape = 2,
|
277
277
|
}
|
278
278
|
|
279
279
|
export class MainModule {
|
@@ -110,6 +110,9 @@
|
|
110
110
|
for (const track of this._customTracks) {
|
111
111
|
track.onEnable?.();
|
112
112
|
}
|
113
|
+
for (const track of this._animationTracks) {
|
114
|
+
track.onEnable?.();
|
115
|
+
}
|
113
116
|
if (this.playOnAwake) {
|
114
117
|
this.play();
|
115
118
|
}
|
@@ -135,6 +138,9 @@
|
|
135
138
|
for (const track of this._customTracks) {
|
136
139
|
track.onDisable?.();
|
137
140
|
}
|
141
|
+
for (const track of this._animationTracks) {
|
142
|
+
track.onDisable?.();
|
143
|
+
}
|
138
144
|
if (this._visibilityChangeEvt)
|
139
145
|
window.removeEventListener('visibilitychange', this._visibilityChangeEvt);
|
140
146
|
}
|
@@ -296,6 +302,12 @@
|
|
296
302
|
if (track.muted) continue;
|
297
303
|
switch (track.type) {
|
298
304
|
case Models.TrackType.Activation:
|
305
|
+
// when the timeline is being disabled or stopped
|
306
|
+
// then we want to leave objects active state as they were
|
307
|
+
// see NE-3241
|
308
|
+
// TODO: support all "post-playback-state" settings an activation track has, this is just "Leave as is"
|
309
|
+
if (!this._isPlaying) continue;
|
310
|
+
|
299
311
|
for (let i = 0; i < track.outputs.length; i++) {
|
300
312
|
const binding = track.outputs[i];
|
301
313
|
if (typeof binding === "object") {
|
@@ -306,8 +318,13 @@
|
|
306
318
|
}
|
307
319
|
}
|
308
320
|
const obj = binding as THREE.Object3D;
|
309
|
-
if (obj.visible !== undefined)
|
310
|
-
obj.visible
|
321
|
+
if (obj.visible !== undefined) {
|
322
|
+
if (obj.visible !== isActive) {
|
323
|
+
obj.visible = isActive;
|
324
|
+
if (debug)
|
325
|
+
console.warn(this.name, "set ActivationTrack-" + i, obj.name, isActive, time);
|
326
|
+
}
|
327
|
+
}
|
311
328
|
}
|
312
329
|
}
|
313
330
|
break;
|
@@ -8,6 +8,7 @@
|
|
8
8
|
import { getParam, resolveUrl } from "../../engine/engine_utils";
|
9
9
|
import { AudioSource } from "../AudioSource";
|
10
10
|
import { Animator } from "../Animator"
|
11
|
+
import { setObjectAnimated } from "../AnimationUtils";
|
11
12
|
|
12
13
|
const debug = getParam("debugtimeline");
|
13
14
|
|
@@ -140,16 +141,23 @@
|
|
140
141
|
private _actionOffsets: Array<AnimationClipOffsetData> = [];
|
141
142
|
private _didBind: boolean = false;
|
142
143
|
private _animator: Animator | null = null;
|
143
|
-
private _animatorWasEnabled?: boolean = false;
|
144
144
|
|
145
145
|
onPauseChanged() {
|
146
|
-
|
147
|
-
|
148
|
-
this._animator.enabled = this.director.isPlaying === false ? this._animatorWasEnabled : false;
|
149
|
-
}
|
146
|
+
if (this._animator)
|
147
|
+
setObjectAnimated(this._animator.gameObject, this.director.isPaused);
|
150
148
|
}
|
151
149
|
|
150
|
+
onEnable() {
|
151
|
+
if (this._animator)
|
152
|
+
setObjectAnimated(this._animator.gameObject, true);
|
153
|
+
}
|
152
154
|
|
155
|
+
onDisable() {
|
156
|
+
if (this._animator)
|
157
|
+
setObjectAnimated(this._animator.gameObject, false);
|
158
|
+
}
|
159
|
+
|
160
|
+
|
153
161
|
createHooks(clipModel: Models.AnimationClipModel, clip) {
|
154
162
|
if (clip.tracks?.length <= 0) {
|
155
163
|
console.warn("No tracks in AnimationClip", clip);
|
@@ -225,11 +233,6 @@
|
|
225
233
|
// We need to disable the animator component in case it also animates
|
226
234
|
// which overrides the timeline
|
227
235
|
this._animator = GameObject.getComponent(this.target, Animator) ?? null;
|
228
|
-
if (this._animator) {
|
229
|
-
this._animatorWasEnabled = this._animator.enabled;
|
230
|
-
this._animator.enabled = false;
|
231
|
-
}
|
232
|
-
else this._animatorWasEnabled = false;
|
233
236
|
}
|
234
237
|
|
235
238
|
// Clip Offsets
|
@@ -285,7 +288,7 @@
|
|
285
288
|
if (!this.mixer) return;
|
286
289
|
this.bind();
|
287
290
|
|
288
|
-
if (this._animator && this.director.isPlaying && this.director.weight > 0) this._animator.enabled = false;
|
291
|
+
// if (this._animator && this.director.isPlaying && this.director.weight > 0) this._animator.enabled = false;
|
289
292
|
|
290
293
|
this._totalOffsetPosition.set(0, 0, 0);
|
291
294
|
this._totalOffsetRotation.set(0, 0, 0, 1);
|
@@ -731,6 +734,13 @@
|
|
731
734
|
didTrigger: boolean[] = [];
|
732
735
|
receivers: Array<SignalReceiver | null> = [];
|
733
736
|
|
737
|
+
// TODO: test when timeline signals are being reset in Unity
|
738
|
+
// onEnable() {
|
739
|
+
// for (let i = 0; i < this.didTrigger?.length; i++) {
|
740
|
+
// this.didTrigger[i] = false;
|
741
|
+
// }
|
742
|
+
// }
|
743
|
+
|
734
744
|
// private _lastTime: number = -1;
|
735
745
|
|
736
746
|
evaluate(time: number) {
|
@@ -750,20 +760,21 @@
|
|
750
760
|
if (model.retroActive) {
|
751
761
|
isActive = td <= 0.000001;
|
752
762
|
}
|
753
|
-
// TODO: handle signal asset at time 0 (only trigger when time is 0)
|
754
|
-
// else if (model.time < .001) {
|
755
|
-
// if (td <= 0.000001 && lastTime > time)
|
756
|
-
// isActive = true;
|
757
|
-
// }
|
758
763
|
else {
|
759
764
|
const abs = Math.abs(td);
|
760
|
-
if (
|
765
|
+
// e.g. if the signal is at frame 0 and the timeline duration also 0 (no tracks, just a signal at frame 0)
|
766
|
+
if (abs === 0) {
|
761
767
|
isActive = true;
|
762
768
|
}
|
769
|
+
else if (abs >= .00001 && abs < estimatedFrameLengthWithPadding) {
|
770
|
+
isActive = true;
|
771
|
+
}
|
763
772
|
}
|
764
773
|
// console.log(time, td, isActive);
|
765
774
|
if (isActive) {
|
766
775
|
if (!wasTriggered) {
|
776
|
+
if (debug)
|
777
|
+
console.log("Trigger signal", time, model.time, model);
|
767
778
|
this.didTrigger[i] = true;
|
768
779
|
// If a signal doesnt have any explicit receivers it will invoke the signal globally
|
769
780
|
if (this.receivers?.length <= 0) {
|