@@ -6,9 +6,7 @@
|
|
6
6
|
let hide = false;
|
7
7
|
if (getParam("noerrors")) hide = true;
|
8
8
|
|
9
|
-
const arContainerClassName = "ar";
|
10
9
|
const globalErrorContainerKey = "needle_engine_global_error_container";
|
11
|
-
const locationRegex = new RegExp(" at .+\/(.+?\.ts)", "g");
|
12
10
|
|
13
11
|
export enum LogType {
|
14
12
|
Log,
|
@@ -6,16 +6,22 @@
|
|
6
6
|
export { LogType, setAllowOverlayMessages };
|
7
7
|
|
8
8
|
/** Displays a debug message on screen for a certain amount of time */
|
9
|
-
export function showBalloonMessage(text: string, logType: LogType = LogType.Log) {
|
9
|
+
export function showBalloonMessage(text: string, logType: LogType = LogType.Log): void {
|
10
10
|
addLog(logType, text);
|
11
11
|
}
|
12
12
|
|
13
13
|
/** Displays a warning message on screen for a certain amount of time */
|
14
|
-
export function showBalloonWarning(text: string) {
|
14
|
+
export function showBalloonWarning(text: string): void {
|
15
15
|
showBalloonMessage(text, LogType.Warn);
|
16
16
|
}
|
17
17
|
|
18
|
+
let _manuallySetDevEnvironment: boolean | undefined;
|
19
|
+
|
18
20
|
/** True when the application runs on a local url */
|
19
|
-
export function isDevEnvironment(){
|
21
|
+
export function isDevEnvironment(): boolean {
|
22
|
+
if (_manuallySetDevEnvironment !== undefined) return _manuallySetDevEnvironment;
|
20
23
|
return isLocalNetwork();
|
24
|
+
}
|
25
|
+
export function setDevEnvironment(val: boolean): void {
|
26
|
+
_manuallySetDevEnvironment = val;
|
21
27
|
}
|
@@ -6,7 +6,7 @@
|
|
6
6
|
import { registerPrefabProvider, syncInstantiate } from "./engine_networking_instantiate.js";
|
7
7
|
import { download } from "./engine_web_api.js";
|
8
8
|
import { getLoader } from "./engine_gltf.js";
|
9
|
-
import { SourceIdentifier } from "./engine_types.js";
|
9
|
+
import { IComponent, SourceIdentifier } from "./engine_types.js";
|
10
10
|
import { destroy, instantiate, InstantiateOptions, isDestroyed } from "./engine_gameobject.js";
|
11
11
|
import { IGameObject } from "./engine_types.js";
|
12
12
|
|
@@ -63,7 +63,23 @@
|
|
63
63
|
|
64
64
|
export class AssetReference {
|
65
65
|
|
66
|
-
|
66
|
+
/**
|
67
|
+
* Get an AssetReference for a URL to be easily loaded. AssetReferences are cached so calling this method multiple times with the same arguments will always return the same AssetReference.
|
68
|
+
*/
|
69
|
+
static getOrCreate(sourceId: SourceIdentifier | IComponent, url: string, context?: Context): AssetReference {
|
70
|
+
|
71
|
+
if (typeof sourceId === "string") {
|
72
|
+
if (!context) {
|
73
|
+
context = Context.Current;
|
74
|
+
if (!context)
|
75
|
+
throw new Error("Context is required when sourceId is a string. When you call this method from a component you can call it with \"getOrCreate(this, url)\" where \"this\" is the component.");
|
76
|
+
}
|
77
|
+
}
|
78
|
+
else {
|
79
|
+
context = sourceId.context as Context;
|
80
|
+
sourceId = sourceId.sourceId!;
|
81
|
+
}
|
82
|
+
|
67
83
|
const fullPath = resolveUrl(sourceId, url);
|
68
84
|
if (debug) console.log("GetOrCreate Addressable from", sourceId, url, "FinalPath=", fullPath);
|
69
85
|
const addressables = context.addressables;
|
@@ -48,11 +48,10 @@
|
|
48
48
|
if (NEEDLE_ENGINE_LICENSE_TYPE === "basic") {
|
49
49
|
try {
|
50
50
|
const licenseUrl = "https://engine.needle.tools/licensing/check?location=" + encodeURIComponent(window.location.href) + "&version=" + VERSION + "&generator=" + encodeURIComponent(GENERATOR);
|
51
|
-
const res = await fetch(licenseUrl
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
}
|
51
|
+
const res = await fetch(licenseUrl, {
|
52
|
+
method: "GET",
|
53
|
+
mode: "no-cors",
|
54
|
+
}).catch();
|
56
55
|
if (res?.status === 200) {
|
57
56
|
applicationIsForbidden = false;
|
58
57
|
if (debug) console.log("License check succeeded");
|
@@ -134,7 +133,7 @@
|
|
134
133
|
|
135
134
|
|
136
135
|
const licenseElementIdentifier = "needle-license-element";
|
137
|
-
const licenseDuration =
|
136
|
+
const licenseDuration = 10000;
|
138
137
|
const licenseDelay = 200;
|
139
138
|
|
140
139
|
async function onNonCommercialVersionDetected(ctx: IContext) {
|
@@ -171,7 +170,7 @@
|
|
171
170
|
const textElement = document.createElement("div");
|
172
171
|
textElement.classList.add("text");
|
173
172
|
// if (!isMobileDevice())
|
174
|
-
//
|
173
|
+
// textElement.innerHTML = "Made with Needle";
|
175
174
|
licenseElement.appendChild(textElement);
|
176
175
|
|
177
176
|
licenseElement.title = "Needle Engine";
|
@@ -181,18 +180,20 @@
|
|
181
180
|
globalThis.open("https://needle.tools", "_blank");
|
182
181
|
});
|
183
182
|
|
184
|
-
|
185
|
-
|
186
|
-
clearInterval(interval);
|
187
|
-
licenseElement?.remove();
|
188
|
-
style?.remove();
|
189
|
-
// show the logo every x minutes
|
190
|
-
const intervalInMinutes = 5;
|
183
|
+
if (hasIndieLicense()) {
|
184
|
+
const removeDelay = licenseDuration + licenseDelay;
|
191
185
|
setTimeout(() => {
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
186
|
+
clearInterval(interval);
|
187
|
+
licenseElement?.remove();
|
188
|
+
style?.remove();
|
189
|
+
// show the logo every x minutes
|
190
|
+
const intervalInMinutes = 5;
|
191
|
+
setTimeout(() => {
|
192
|
+
if (ctx.domElement.parentNode)
|
193
|
+
insertNonCommercialUseHint(ctx);
|
194
|
+
}, 1000 * 60 * intervalInMinutes)
|
195
|
+
}, removeDelay);
|
196
|
+
}
|
196
197
|
|
197
198
|
}
|
198
199
|
let lastLogTime = 0;
|
@@ -251,45 +252,44 @@
|
|
251
252
|
|
252
253
|
${selector}:hover {
|
253
254
|
cursor: pointer;
|
254
|
-
transition: all 0.
|
255
|
+
transition: all 0.3s ease-in-out !important;
|
255
256
|
}
|
256
257
|
|
257
258
|
${selector}, ${selector} > * {
|
258
259
|
display: inline-block !important;
|
259
260
|
visibility: visible !important;
|
260
|
-
background: none !important;
|
261
261
|
border: none !important;
|
262
262
|
text-decoration: none !important;
|
263
263
|
vertical-align: middle !important;
|
264
264
|
}
|
265
265
|
|
266
|
-
@keyframes license-animation {
|
266
|
+
@keyframes license-animation-text {
|
267
267
|
1% {
|
268
268
|
opacity: 0;
|
269
269
|
}
|
270
270
|
2.5% {
|
271
271
|
opacity: 1;
|
272
272
|
}
|
273
|
-
|
273
|
+
100% {
|
274
274
|
opacity: 1;
|
275
275
|
}
|
276
|
-
99% {
|
277
|
-
opacity: 0;
|
278
|
-
}
|
279
276
|
}
|
280
277
|
${selector} .text {
|
278
|
+
position: relative;
|
279
|
+
display: inline-block;
|
281
280
|
opacity: 0;
|
282
|
-
animation: license-animation;
|
283
|
-
animation-iteration-count: 1;
|
281
|
+
animation: license-animation-text;
|
284
282
|
animation-duration: ${(licenseDuration / 1000)}s;
|
285
283
|
animation-delay: ${licenseDelay / 1000}s;
|
286
|
-
animation-
|
287
|
-
mix-blend-mode: difference;
|
288
|
-
color: rgb(0, 0, 0);
|
289
|
-
mix-blend-mode: difference;
|
284
|
+
animation-fill-mode: forwards;
|
290
285
|
line-height: 1em;
|
291
286
|
margin-left: -3px;
|
292
|
-
|
287
|
+
color: rgba(20,20,20,1);
|
288
|
+
font-size: 14px;
|
289
|
+
font-weight: 800;
|
290
|
+
background-color: rgba(220, 220, 220, .8);
|
291
|
+
// padding: .51em .6em .43em .6em;
|
292
|
+
border-radius: .7em;
|
293
293
|
}
|
294
294
|
|
295
295
|
${selector} .text .non-commercial {
|
@@ -303,34 +303,30 @@
|
|
303
303
|
transform: translate(0px, 10px);
|
304
304
|
pointer-events: none;
|
305
305
|
}
|
306
|
-
|
306
|
+
3% {
|
307
307
|
transform: translate(0, 0px);
|
308
308
|
pointer-events: all;
|
309
309
|
opacity: 1;
|
310
310
|
transform: scale(1.1)
|
311
311
|
}
|
312
|
-
|
312
|
+
12% {
|
313
313
|
transform: scale(1)
|
314
314
|
}
|
315
|
-
|
315
|
+
100% {
|
316
316
|
opacity: 1;
|
317
317
|
pointer-events: all;
|
318
318
|
transform: scale(1)
|
319
319
|
}
|
320
|
-
100% {
|
321
|
-
pointer-events: none;
|
322
|
-
opacity: 0;
|
323
|
-
}
|
324
320
|
}
|
325
321
|
|
326
322
|
${selector} .logo {
|
327
323
|
opacity: 0;
|
328
324
|
pointer-events: none;
|
329
325
|
animation: logo-animation;
|
330
|
-
animation-iteration-count: 1;
|
331
326
|
animation-duration: ${(licenseDuration / 1000)}s;
|
332
327
|
animation-delay: ${licenseDelay / 1000}s;
|
333
328
|
animation-easing: ease-in-out;
|
329
|
+
animation-fill-mode: forwards;
|
334
330
|
}
|
335
331
|
|
336
332
|
${selector} .logo {
|
@@ -339,12 +335,12 @@
|
|
339
335
|
border-radius: 50% !important;
|
340
336
|
background-color: transparent !important;
|
341
337
|
padding: 5px !important;
|
342
|
-
transition: all 0.
|
338
|
+
transition: all 0.2s ease-in-out !important;
|
343
339
|
}
|
344
340
|
|
345
341
|
${selector}:hover .logo {
|
346
|
-
transition: all 0.
|
347
|
-
transform: scale(1.
|
342
|
+
transition: all 0.2s ease-in-out !important;
|
343
|
+
transform: scale(1.02) !important;
|
348
344
|
cursor: pointer !important;
|
349
345
|
}
|
350
346
|
`
|
@@ -354,9 +350,7 @@
|
|
354
350
|
|
355
351
|
async function sendUsageMessageToAnalyticsBackend() {
|
356
352
|
try {
|
357
|
-
const
|
358
|
-
const res = await fetch(analyticsBackendUrlForward);
|
359
|
-
const analyticsUrl = await res.text();
|
353
|
+
const analyticsUrl = "https://needle-engine-analytics-v2-r26roub2hq-lz.a.run.app";
|
360
354
|
if (analyticsUrl) {
|
361
355
|
if (debug) console.log("Analytics backend url", analyticsUrl);
|
362
356
|
|
@@ -1,14 +1,11 @@
|
|
1
1
|
|
2
2
|
|
3
|
-
// const testUrls = ["https://192.254.384.122:3000/", "https://my-glitch-page.glitch.me/"]
|
4
|
-
// for (let url of testUrls)
|
5
|
-
// console.log("Testing url: " + url, isLocalNetwork(url));
|
6
3
|
|
7
4
|
const localNetworkResults = new Map<string, boolean>();
|
8
5
|
|
9
6
|
export function isLocalNetwork(hostname = globalThis.location?.hostname) {
|
10
|
-
if(localNetworkResults.has(hostname)) return localNetworkResults.get(hostname)!;
|
11
|
-
const isLocalNetwork = new RegExp("[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|localhost"
|
7
|
+
if (localNetworkResults.has(hostname)) return localNetworkResults.get(hostname)!;
|
8
|
+
const isLocalNetwork = new RegExp("([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\:[0-9]{1,5})|localhost").test(hostname);
|
12
9
|
localNetworkResults.set(hostname, isLocalNetwork);
|
13
10
|
if (isLocalNetwork === true) return true;
|
14
11
|
return false;
|
@@ -16,4 +13,11 @@
|
|
16
13
|
|
17
14
|
export function isHostedOnGlitch() {
|
18
15
|
return window.location.hostname.includes("glitch.me");
|
19
|
-
}
|
16
|
+
}
|
17
|
+
|
18
|
+
// const testUrls = [
|
19
|
+
// "https://192.254.384.122:3000/",
|
20
|
+
// "https://my-glitch-page.glitch.me/",
|
21
|
+
// ]
|
22
|
+
// for (let url of testUrls)
|
23
|
+
// console.log("Testing url: " + url, isLocalNetwork(url));
|
@@ -257,7 +257,11 @@
|
|
257
257
|
|
258
258
|
// if the pointer didnt change, theoretically we would need to check the camera as well
|
259
259
|
if (!args.isDown && !args.isUp && !args.isClicked && !args.isPressed && !args.positionDelta.x && !args.positionDelta.y)
|
260
|
+
{
|
261
|
+
this.objectsHoveredThisFrame.length = 0;
|
262
|
+
this.objectsHoveredThisFrame.push(...this.objectsHoveredLastFrame);
|
260
263
|
return
|
264
|
+
}
|
261
265
|
|
262
266
|
if(debug) console.log("EventSystem: raycast");
|
263
267
|
const hits = this.performRaycast(null);
|
@@ -1,3 +1,4 @@
|
|
1
1
|
export * from "./SignalAsset.js"
|
2
2
|
export * from "./TimelineTracks.js"
|
3
|
-
export * from "./TimelineModels.js"
|
3
|
+
export * from "./TimelineModels.js"
|
4
|
+
export { type ITimelineAnimationCallbacks as ITimelineAnimationOverride } from "./PlayableDirector.js"
|
@@ -11,7 +11,7 @@
|
|
11
11
|
import { AxesHelper, BufferGeometry, Color, Material, Matrix4, Mesh, MeshStandardMaterial, Object3D, OneMinusDstAlphaFactor, PlaneGeometry, Quaternion, Sprite, SpriteMaterial, Vector3, Vector4 } from "three";
|
12
12
|
import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldScale } from "../engine/engine_three_utils.js";
|
13
13
|
import { assign } from "../engine/engine_serialization_core.js";
|
14
|
-
import { BatchedParticleRenderer, Behavior, BillBoardSettings, BurstParameters, ColorGenerator, ConstantColor, ConstantValue, EmissionState, EmitSubParticleSystem, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, IntervalValue, MeshSettings, Particle, ParticleEmitter, ParticleSystem as _ParticleSystem, ParticleSystemParameters, PointEmitter, RecordState, RenderMode, RotationGenerator, SizeOverLife, TrailBatch, TrailParticle, TrailSettings, ValueGenerator } from "three.quarks";
|
14
|
+
import { BatchedRenderer, BatchedParticleRenderer, Behavior, BillBoardSettings, BurstParameters, ColorGenerator, ConstantColor, ConstantValue, EmissionState, EmitSubParticleSystem, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, IntervalValue, MeshSettings, Particle, ParticleEmitter, ParticleSystem as _ParticleSystem, ParticleSystemParameters, PointEmitter, RecordState, RenderMode, RotationGenerator, SizeOverLife, TrailBatch, TrailParticle, TrailSettings, ValueGenerator } from "three.quarks";
|
15
15
|
import { createFlatTexture } from "../engine/engine_shaders.js";
|
16
16
|
import { Mathf } from "../engine/engine_math.js";
|
17
17
|
import { Context } from "../engine/engine_setup.js";
|
@@ -269,6 +269,8 @@
|
|
269
269
|
}
|
270
270
|
toJSON() { throw new Error("Method not implemented."); }
|
271
271
|
clone(): Behavior { throw new Error("Method not implemented."); }
|
272
|
+
reset() {
|
273
|
+
}
|
272
274
|
}
|
273
275
|
|
274
276
|
const $startFrame = Symbol("startFrame")
|
@@ -346,7 +348,9 @@
|
|
346
348
|
let size = 1;
|
347
349
|
if (this.system.sizeOverLifetime.enabled)
|
348
350
|
size *= this.system.sizeOverLifetime.evaluate(age01, undefined, particle[$sizeLerpFactor]).x;
|
349
|
-
|
351
|
+
let scaleFactor = 1;
|
352
|
+
if (this.system.renderer.renderMode !== ParticleSystemRenderMode.Mesh)
|
353
|
+
scaleFactor = this.system.worldScale.x / this.system.cameraScale;
|
350
354
|
particle.size = particle.startSize * size * scaleFactor;
|
351
355
|
if (this.system.localspace) {
|
352
356
|
const scale = getLocalSimulationScale(this.system, localScaleVec3);
|
@@ -544,6 +548,10 @@
|
|
544
548
|
this.emission = new ParticleSystemEmissionOverTime(this.system);
|
545
549
|
}
|
546
550
|
|
551
|
+
get prewarm() { return false; } // force disable three.quark prewarm, we have our own!
|
552
|
+
get material() { return this.system.renderer.getMaterial(this.system.trails.enabled) as Material;}
|
553
|
+
get layers() { return this.system.gameObject.layers; }
|
554
|
+
|
547
555
|
update() {
|
548
556
|
this.emission.update();
|
549
557
|
}
|
@@ -575,9 +583,9 @@
|
|
575
583
|
switch (this.system.renderer.renderMode) {
|
576
584
|
case ParticleSystemRenderMode.Billboard: return RenderMode.BillBoard;
|
577
585
|
case ParticleSystemRenderMode.Stretch: return RenderMode.StretchedBillBoard;
|
578
|
-
case ParticleSystemRenderMode.HorizontalBillboard: return RenderMode.
|
579
|
-
case ParticleSystemRenderMode.VerticalBillboard: return RenderMode.
|
580
|
-
case ParticleSystemRenderMode.Mesh: return RenderMode.
|
586
|
+
case ParticleSystemRenderMode.HorizontalBillboard: return RenderMode.BillBoard;
|
587
|
+
case ParticleSystemRenderMode.VerticalBillboard: return RenderMode.BillBoard;
|
588
|
+
case ParticleSystemRenderMode.Mesh: return RenderMode.Mesh;
|
581
589
|
}
|
582
590
|
return RenderMode.BillBoard;
|
583
591
|
}
|
@@ -587,7 +595,7 @@
|
|
587
595
|
};
|
588
596
|
get speedFactor() { return this.system.main.simulationSpeed; }
|
589
597
|
get texture(): THREE.Texture {
|
590
|
-
const mat = this.
|
598
|
+
const mat = this.material;
|
591
599
|
if (mat && mat["map"]) {
|
592
600
|
const tex = mat["map"]!;
|
593
601
|
tex.premultiplyAlpha = false;
|
@@ -811,7 +819,7 @@
|
|
811
819
|
}
|
812
820
|
|
813
821
|
private _renderer!: ParticleSystemRenderer;
|
814
|
-
private _batchSystem?:
|
822
|
+
private _batchSystem?: BatchedRenderer;
|
815
823
|
private _particleSystem?: _ParticleSystem;
|
816
824
|
private _interface!: ParticleSystemInterface;
|
817
825
|
|
@@ -882,13 +890,14 @@
|
|
882
890
|
this._batchSystem.name = this.gameObject.name;
|
883
891
|
this._container.add(this._batchSystem);
|
884
892
|
this._interface = new ParticleSystemInterface(this);
|
885
|
-
this._particleSystem = new _ParticleSystem(this.
|
893
|
+
this._particleSystem = new _ParticleSystem(this._interface);
|
886
894
|
this._particleSystem.addBehavior(new SizeBehaviour(this));
|
887
895
|
this._particleSystem.addBehavior(new ColorBehaviour(this));
|
888
896
|
this._particleSystem.addBehavior(new TextureSheetAnimationBehaviour(this));
|
889
897
|
this._particleSystem.addBehavior(new RotationBehaviour(this));
|
890
898
|
this._particleSystem.addBehavior(new VelocityBehaviour(this));
|
891
899
|
this._particleSystem.addBehavior(new TrailBehaviour(this));
|
900
|
+
this._batchSystem.addSystem(this._particleSystem);
|
892
901
|
|
893
902
|
const emitter = this._particleSystem.emitter;
|
894
903
|
this.context.scene.add(emitter);
|
@@ -946,7 +955,7 @@
|
|
946
955
|
const timeToSimulate = Math.min(Math.max(duration, lifetime) / Math.max(.01, this.main.simulationSpeed), maxDurationToPrewarm);
|
947
956
|
const framesToSimulate = Math.ceil(timeToSimulate / dt);
|
948
957
|
const startTime = Date.now();
|
949
|
-
if (debug
|
958
|
+
if (debug)
|
950
959
|
console.log(`Particles ${this.name} - Prewarm for ${framesToSimulate} frames (${timeToSimulate} sec). Duration: ${duration}, Lifetime: ${lifetime}`);
|
951
960
|
for (let i = 0; i < framesToSimulate; i++) {
|
952
961
|
if (this.currentParticles >= this.maxParticles) break;
|
@@ -993,6 +1002,7 @@
|
|
993
1002
|
|
994
1003
|
private lastMaterialVersion: number = -1;
|
995
1004
|
private onUpdate() {
|
1005
|
+
|
996
1006
|
const mat = this.renderer.getMaterial(this.trails.enabled);
|
997
1007
|
if (mat && mat.version != this.lastMaterialVersion && this._particleSystem) {
|
998
1008
|
this.lastMaterialVersion = mat.version;
|
@@ -68,6 +68,9 @@
|
|
68
68
|
toJSON(): any {
|
69
69
|
}
|
70
70
|
|
71
|
+
reset() {
|
72
|
+
}
|
73
|
+
|
71
74
|
private run(particle: Particle) {
|
72
75
|
if (this.subSystem.currentParticles >= this.subSystem.main.maxParticles)
|
73
76
|
return;
|
@@ -8,7 +8,7 @@
|
|
8
8
|
import * as Tracks from "./TimelineTracks.js";
|
9
9
|
import { deepClone, delay, getParam } from '../../engine/engine_utils.js';
|
10
10
|
import { GuidsMap } from '../../engine/engine_types.js';
|
11
|
-
import { Object3D } from 'three';
|
11
|
+
import { Object3D, Quaternion, Vector3 } from 'three';
|
12
12
|
import { isLocalNetwork } from '../../engine/engine_networking_utils.js';
|
13
13
|
import { FrameEvent } from '../../engine/engine_context.js';
|
14
14
|
|
@@ -597,4 +597,36 @@
|
|
597
597
|
}
|
598
598
|
}
|
599
599
|
|
600
|
+
|
601
|
+
|
602
|
+
/** Experimental support for overriding timeline animation data (position or rotation) */
|
603
|
+
readonly animationCallbackReceivers: ITimelineAnimationCallbacks[] = [];
|
604
|
+
/** Experimental: Receive callbacks for timeline animation. Allows modification of final value */
|
605
|
+
registerAnimationCallback(receiver: ITimelineAnimationCallbacks) { this.animationCallbackReceivers.push(receiver); }
|
606
|
+
/** Experimental: Unregister callbacks for timeline animation. Allows modification of final value */
|
607
|
+
unregisterAnimationCallback(receiver: ITimelineAnimationCallbacks) {
|
608
|
+
const index = this.animationCallbackReceivers.indexOf(receiver);
|
609
|
+
if (index === -1) return;
|
610
|
+
this.animationCallbackReceivers.splice(index, 1);
|
611
|
+
}
|
600
612
|
}
|
613
|
+
|
614
|
+
/**
|
615
|
+
* Experimental interface for receiving timeline animation callbacks. Register at the PlayableDirector
|
616
|
+
*/
|
617
|
+
export interface ITimelineAnimationCallbacks {
|
618
|
+
/**
|
619
|
+
* @param director The director that is playing the timeline
|
620
|
+
* @param target The target object that is being animated
|
621
|
+
* @param time The current time of the timeline
|
622
|
+
* @param rotation The evaluated rotation of the target object at the current time
|
623
|
+
*/
|
624
|
+
onTimelineRotation?(director: PlayableDirector, target: Object3D, time: number, rotation: Quaternion);
|
625
|
+
/**
|
626
|
+
* @param director The director that is playing the timeline
|
627
|
+
* @param target The target object that is being animated
|
628
|
+
* @param time The current time of the timeline
|
629
|
+
* @param position The evaluated position of the target object at the current time
|
630
|
+
*/
|
631
|
+
onTimelinePosition?(director: PlayableDirector, target: Object3D, time: number, position: Vector3);
|
632
|
+
}
|
@@ -1189,6 +1189,7 @@
|
|
1189
1189
|
const normalScale = material instanceof MeshStandardMaterial ? (material.normalScale ? material.normalScale.x * 2 : 2) : 2;
|
1190
1190
|
const normalScaleValueString = normalScale.toFixed( PRECISION );
|
1191
1191
|
const normalBiasString = (-1 * (normalScale / 2)).toFixed( PRECISION );
|
1192
|
+
const normalBiasZString = (1 - normalScale).toFixed( PRECISION );
|
1192
1193
|
|
1193
1194
|
return `
|
1194
1195
|
${needsTextureTransform ? `def Shader "Transform2d_${mapType}" (
|
@@ -1216,7 +1217,7 @@
|
|
1216
1217
|
` : `` }
|
1217
1218
|
${needsNormalScaleAndBias ? `
|
1218
1219
|
float4 inputs:scale = (${normalScaleValueString}, ${normalScaleValueString}, ${normalScaleValueString}, 1)
|
1219
|
-
float4 inputs:bias = (${normalBiasString}, ${normalBiasString}, ${
|
1220
|
+
float4 inputs:bias = (${normalBiasString}, ${normalBiasString}, ${normalBiasZString}, 0)
|
1220
1221
|
` : `` }
|
1221
1222
|
token inputs:wrapS = "${ WRAPPINGS[ texture.wrapS ] }"
|
1222
1223
|
token inputs:wrapT = "${ WRAPPINGS[ texture.wrapT ] }"
|
@@ -1,10 +1,9 @@
|
|
1
1
|
import { PlayableDirector } from "./PlayableDirector.js";
|
2
2
|
import * as Models from "./TimelineModels.js";
|
3
|
-
import * as THREE from 'three';
|
4
3
|
import { GameObject } from "../Component.js";
|
5
4
|
import { Context } from "../../engine/engine_setup.js";
|
6
5
|
import { SignalReceiver } from "./SignalAsset.js";
|
7
|
-
import { AnimationClip, Quaternion, Vector3 } from "three";
|
6
|
+
import { Audio, AudioListener, AnimationAction, AnimationClip, AnimationMixer, AudioLoader, Euler, Object3D, Quaternion, QuaternionKeyframeTrack, Vector3, VectorKeyframeTrack } from "three";
|
8
7
|
import { getParam, resolveUrl } from "../../engine/engine_utils.js";
|
9
8
|
import { AudioSource } from "../AudioSource.js";
|
10
9
|
import { Animator } from "../Animator.js"
|
@@ -88,17 +87,17 @@
|
|
88
87
|
|
89
88
|
class AnimationClipOffsetData {
|
90
89
|
clip: AnimationClip;
|
91
|
-
rootPositionOffset?:
|
92
|
-
rootQuaternionOffset?:
|
90
|
+
rootPositionOffset?: Vector3;
|
91
|
+
rootQuaternionOffset?: Quaternion;
|
93
92
|
get hasOffsets(): boolean { return this.rootPositionOffset !== undefined || this.rootQuaternionOffset !== undefined; }
|
94
93
|
|
95
94
|
// not necessary
|
96
|
-
rootStartPosition?:
|
97
|
-
rootEndPosition?:
|
98
|
-
rootStartQuaternion?:
|
99
|
-
rootEndQuaternion?:
|
95
|
+
rootStartPosition?: Vector3;
|
96
|
+
rootEndPosition?: Vector3;
|
97
|
+
rootStartQuaternion?: Quaternion;
|
98
|
+
rootEndQuaternion?: Quaternion;
|
100
99
|
|
101
|
-
constructor(action:
|
100
|
+
constructor(action: AnimationAction) {
|
102
101
|
const clip = action.getClip();
|
103
102
|
this.clip = clip;
|
104
103
|
const root = action.getRoot();
|
@@ -109,20 +108,20 @@
|
|
109
108
|
for (const track of clip.tracks) {
|
110
109
|
if (track.times.length <= 0) continue;
|
111
110
|
if (track.name.endsWith(rootPositionTrackName)) {
|
112
|
-
this.rootStartPosition = new
|
113
|
-
this.rootEndPosition = new
|
111
|
+
this.rootStartPosition = new Vector3().fromArray(track.values, 0);
|
112
|
+
this.rootEndPosition = new Vector3().fromArray(track.values, track.values.length - 3);
|
114
113
|
this.rootPositionOffset = this.rootEndPosition.clone().sub(this.rootStartPosition);
|
115
114
|
if (debug)
|
116
115
|
console.log(this.rootPositionOffset);
|
117
116
|
// this.rootPositionOffset.set(0, 0, 0);
|
118
117
|
}
|
119
118
|
else if (track.name.endsWith(rootRotationTrackName)) {
|
120
|
-
this.rootStartQuaternion = new
|
121
|
-
this.rootEndQuaternion = new
|
119
|
+
this.rootStartQuaternion = new Quaternion().fromArray(track.values, 0);
|
120
|
+
this.rootEndQuaternion = new Quaternion().fromArray(track.values, track.values.length - 4);
|
122
121
|
this.rootQuaternionOffset = this.rootEndQuaternion.clone().multiply(this.rootStartQuaternion);
|
123
122
|
|
124
123
|
if (debug) {
|
125
|
-
const euler = new
|
124
|
+
const euler = new Euler().setFromQuaternion(this.rootQuaternionOffset);
|
126
125
|
console.log("ROT", euler);
|
127
126
|
}
|
128
127
|
}
|
@@ -135,11 +134,11 @@
|
|
135
134
|
models: Array<Models.ClipModel> = [];
|
136
135
|
trackOffset?: Models.TrackOffset;
|
137
136
|
|
138
|
-
target?:
|
137
|
+
target?: Object3D;
|
139
138
|
/** The AnimationMixer, should be shared with the animator if an animator is bound */
|
140
|
-
mixer?:
|
141
|
-
clips: Array<
|
142
|
-
actions: Array<
|
139
|
+
mixer?: AnimationMixer;
|
140
|
+
clips: Array<AnimationClip> = [];
|
141
|
+
actions: Array<AnimationAction> = [];
|
143
142
|
|
144
143
|
/** holds data/info about clips differences */
|
145
144
|
private _actionOffsets: Array<AnimationClipOffsetData> = [];
|
@@ -185,7 +184,7 @@
|
|
185
184
|
// TODO: this currently assumes that there is only one root always that has offsets so it only does create the interpolator for the first track which might be incorrect. In general it would probably be better if we would not create additional tracks but apply the offsets for these objects elsewhere!?
|
186
185
|
|
187
186
|
if (!foundPositionTrack || !foundRotationTrack) {
|
188
|
-
const root = this.mixer?.getRoot() as
|
187
|
+
const root = this.mixer?.getRoot() as Object3D;
|
189
188
|
const track = clip.tracks[0];
|
190
189
|
const indexOfProperty = track.name.lastIndexOf(".");
|
191
190
|
const baseName = track.name.substring(0, indexOfProperty);
|
@@ -202,7 +201,7 @@
|
|
202
201
|
if (debug) console.warn("Create position track", objName, targetObj);
|
203
202
|
// apply initial local position so it doesnt get flipped or otherwise changed
|
204
203
|
const pos = targetObj.position;
|
205
|
-
const track = new
|
204
|
+
const track = new VectorKeyframeTrack(trackName, [0, clip.duration], [pos.x, pos.y, pos.z, pos.x, pos.y, pos.z]);
|
206
205
|
clip.tracks.push(track);
|
207
206
|
this.createPositionInterpolant(clip, clipModel, track);
|
208
207
|
}
|
@@ -210,7 +209,7 @@
|
|
210
209
|
const trackName = clip.tracks[0].name.substring(0, indexOfProperty) + ".quaternion";
|
211
210
|
if (debug) console.warn("Create quaternion track", objName, targetObj);
|
212
211
|
const rot = targetObj.quaternion;
|
213
|
-
const track = new
|
212
|
+
const track = new QuaternionKeyframeTrack(trackName, [0, clip.duration], [rot.x, rot.y, rot.z, rot.w, rot.x, rot.y, rot.z, rot.w]);
|
214
213
|
clip.tracks.push(track);
|
215
214
|
this.createRotationInterpolant(clip, clipModel, track);
|
216
215
|
}
|
@@ -224,7 +223,7 @@
|
|
224
223
|
if (debug) console.log(this.models);
|
225
224
|
|
226
225
|
// the object being animated
|
227
|
-
if (this.mixer) this.target = this.mixer.getRoot() as
|
226
|
+
if (this.mixer) this.target = this.mixer.getRoot() as Object3D;
|
228
227
|
else console.warn("No mixer was assigned to animation track")
|
229
228
|
|
230
229
|
for (const action of this.actions) {
|
@@ -265,13 +264,13 @@
|
|
265
264
|
const pos = this.trackOffset.position as any;
|
266
265
|
if (pos) {
|
267
266
|
if (!pos.isVector3) {
|
268
|
-
this.trackOffset.position = new
|
267
|
+
this.trackOffset.position = new Vector3(pos.x, pos.y, pos.z);
|
269
268
|
}
|
270
269
|
}
|
271
270
|
const rot = this.trackOffset.rotation as any;
|
272
271
|
if (rot) {
|
273
272
|
if (!rot.isQuaternion) {
|
274
|
-
this.trackOffset.rotation = new
|
273
|
+
this.trackOffset.rotation = new Quaternion(rot.x, rot.y, rot.z, rot.w);
|
275
274
|
}
|
276
275
|
}
|
277
276
|
}
|
@@ -279,20 +278,20 @@
|
|
279
278
|
|
280
279
|
private _useclipOffsets: boolean = true;
|
281
280
|
|
282
|
-
private _totalOffsetPosition:
|
283
|
-
private _totalOffsetRotation:
|
284
|
-
private _totalOffsetPosition2:
|
285
|
-
private _totalOffsetRotation2:
|
286
|
-
private _summedPos = new
|
287
|
-
private _tempPos = new
|
288
|
-
private _summedRot = new
|
289
|
-
private _tempRot = new
|
290
|
-
private _clipRotQuat = new
|
281
|
+
private _totalOffsetPosition: Vector3 = new Vector3();
|
282
|
+
private _totalOffsetRotation: Quaternion = new Quaternion();
|
283
|
+
private _totalOffsetPosition2: Vector3 = new Vector3();
|
284
|
+
private _totalOffsetRotation2: Quaternion = new Quaternion();
|
285
|
+
private _summedPos = new Vector3();
|
286
|
+
private _tempPos = new Vector3();
|
287
|
+
private _summedRot = new Quaternion();
|
288
|
+
private _tempRot = new Quaternion();
|
289
|
+
private _clipRotQuat = new Quaternion();
|
291
290
|
|
292
291
|
evaluate(time: number) {
|
293
292
|
if (this.track.muted) return;
|
294
293
|
if (!this.mixer) return;
|
295
|
-
|
294
|
+
|
296
295
|
this.bind();
|
297
296
|
|
298
297
|
// if (this._animator && this.director.isPlaying && this.director.weight > 0) this._animator.enabled = false;
|
@@ -349,7 +348,7 @@
|
|
349
348
|
break;
|
350
349
|
case Models.ClipExtrapolation.Loop:
|
351
350
|
// TODO: this is not correct yet
|
352
|
-
time += model.start;
|
351
|
+
time += model.start;
|
353
352
|
handleLoop = true;
|
354
353
|
break;
|
355
354
|
default:
|
@@ -442,7 +441,7 @@
|
|
442
441
|
tempPos.applyQuaternion(this._clipRotQuat);
|
443
442
|
|
444
443
|
if (offsets.rootQuaternionOffset) {
|
445
|
-
// console.log(new
|
444
|
+
// console.log(new Euler().setFromQuaternion(offsets.rootQuaternionOffset).y.toFixed(2));
|
446
445
|
tempRot.copy(offsets.rootQuaternionOffset);
|
447
446
|
summedRot.multiply(tempRot);
|
448
447
|
}
|
@@ -455,7 +454,7 @@
|
|
455
454
|
totalRotation.multiply(summedRot);
|
456
455
|
|
457
456
|
if (clipModel.position)
|
458
|
-
summedPos.add(clipModel.position as
|
457
|
+
summedPos.add(clipModel.position as Vector3);
|
459
458
|
totalPosition.add(summedPos);
|
460
459
|
}
|
461
460
|
|
@@ -484,9 +483,9 @@
|
|
484
483
|
|
485
484
|
private createRotationInterpolant(_clip: AnimationClip, _clipModel: Models.AnimationClipModel, track: any) {
|
486
485
|
const createInterpolantOriginal = track.createInterpolant.bind(track);
|
487
|
-
const quat:
|
486
|
+
const quat: Quaternion = new Quaternion();
|
488
487
|
this.ensureTrackOffsets();
|
489
|
-
const trackOffsetRot:
|
488
|
+
const trackOffsetRot: Quaternion | null = this.trackOffset?.rotation as Quaternion;
|
490
489
|
track.createInterpolant = () => {
|
491
490
|
const createdInterpolant: any = createInterpolantOriginal();
|
492
491
|
const interpolate = createdInterpolant.evaluate.bind(createdInterpolant);
|
@@ -495,13 +494,19 @@
|
|
495
494
|
const res = interpolate(time);
|
496
495
|
quat.set(res[0], res[1], res[2], res[3]);
|
497
496
|
quat.premultiply(this._totalOffsetRotation);
|
498
|
-
// console.log(new
|
497
|
+
// console.log(new Euler().setFromQuaternion(quat).y.toFixed(2));
|
499
498
|
if (trackOffsetRot) quat.premultiply(trackOffsetRot);
|
499
|
+
|
500
|
+
if (this.director.animationCallbackReceivers) {
|
501
|
+
for (const rec of this.director.animationCallbackReceivers) {
|
502
|
+
rec?.onTimelineRotation?.call(rec, this.director, this.target!, time, quat);
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
500
506
|
res[0] = quat.x;
|
501
507
|
res[1] = quat.y;
|
502
508
|
res[2] = quat.z;
|
503
509
|
res[3] = quat.w;
|
504
|
-
|
505
510
|
return res;
|
506
511
|
};
|
507
512
|
return createdInterpolant;
|
@@ -510,10 +515,10 @@
|
|
510
515
|
|
511
516
|
private createPositionInterpolant(clip: AnimationClip, clipModel: Models.AnimationClipModel, track: any) {
|
512
517
|
const createInterpolantOriginal = track.createInterpolant.bind(track);
|
513
|
-
const currentPosition:
|
518
|
+
const currentPosition: Vector3 = new Vector3();
|
514
519
|
this.ensureTrackOffsets();
|
515
|
-
const trackOffsetRot:
|
516
|
-
const trackOffsetPos:
|
520
|
+
const trackOffsetRot: Quaternion | null = this.trackOffset?.rotation as Quaternion;
|
521
|
+
const trackOffsetPos: Vector3 | null = this.trackOffset?.position as Vector3;
|
517
522
|
let startOffset: Vector3 | null | undefined = undefined;
|
518
523
|
track.createInterpolant = () => {
|
519
524
|
const createdInterpolant: any = createInterpolantOriginal();
|
@@ -540,6 +545,11 @@
|
|
540
545
|
currentPosition.y += trackOffsetPos.y;
|
541
546
|
currentPosition.z += trackOffsetPos.z;
|
542
547
|
}
|
548
|
+
if (this.director.animationCallbackReceivers) {
|
549
|
+
for (const rec of this.director.animationCallbackReceivers) {
|
550
|
+
rec?.onTimelinePosition?.call(rec, this.director, this.target!, time, currentPosition);
|
551
|
+
}
|
552
|
+
}
|
543
553
|
res[0] = currentPosition.x;
|
544
554
|
res[1] = currentPosition.y;
|
545
555
|
res[2] = currentPosition.z;
|
@@ -556,12 +566,12 @@
|
|
556
566
|
export class AudioTrackHandler extends TrackHandler {
|
557
567
|
models: Array<Models.ClipModel> = [];
|
558
568
|
|
559
|
-
listener!:
|
560
|
-
audio: Array<
|
569
|
+
listener!: AudioListener;
|
570
|
+
audio: Array<Audio> = [];
|
561
571
|
audioContextTimeOffset: Array<number> = [];
|
562
572
|
lastTime: number = 0;
|
563
573
|
|
564
|
-
private _audioLoader:
|
574
|
+
private _audioLoader: AudioLoader | null = null;
|
565
575
|
|
566
576
|
private getAudioFilePath(path: string) {
|
567
577
|
// TODO: this should be the timeline asset location probably which MIGHT be different
|
@@ -578,7 +588,7 @@
|
|
578
588
|
}
|
579
589
|
|
580
590
|
addModel(model: Models.ClipModel) {
|
581
|
-
const audio = new
|
591
|
+
const audio = new Audio(this.listener as any);
|
582
592
|
this.audio.push(audio);
|
583
593
|
this.models.push(model);
|
584
594
|
}
|
@@ -653,7 +663,7 @@
|
|
653
663
|
else {
|
654
664
|
const targetOffset = model.clipIn + (time - model.start) * model.timeScale;
|
655
665
|
// seems it's non-trivial to get the right time from audio sources;
|
656
|
-
// https://github.com/mrdoob/
|
666
|
+
// https://github.com/mrdoob/js/blob/master/src/audio/Audio.js#L170
|
657
667
|
const currentTime = audio.context.currentTime - audio["_startedAt"] + audio.offset;
|
658
668
|
const diff = Math.abs(targetOffset - currentTime);
|
659
669
|
|
@@ -720,9 +730,9 @@
|
|
720
730
|
AudioTrackHandler._audioBuffers.clear();
|
721
731
|
}
|
722
732
|
|
723
|
-
private handleAudioLoading(model: Models.ClipModel, audio:
|
733
|
+
private handleAudioLoading(model: Models.ClipModel, audio: Audio): Promise<AudioBuffer | null> | null {
|
724
734
|
if (!this._audioLoader) {
|
725
|
-
this._audioLoader = new
|
735
|
+
this._audioLoader = new AudioLoader();
|
726
736
|
}
|
727
737
|
// TODO: maybe we should cache the loaders / buffers here by path
|
728
738
|
const path = this.getAudioFilePath(model.asset.clip);
|
@@ -861,7 +871,7 @@
|
|
861
871
|
const clipTime = this.getClipTime(time, model);
|
862
872
|
|
863
873
|
if (asset.controlActivation) {
|
864
|
-
const obj = asset.sourceObject as
|
874
|
+
const obj = asset.sourceObject as Object3D;
|
865
875
|
obj.visible = true;
|
866
876
|
}
|
867
877
|
|
@@ -881,7 +891,7 @@
|
|
881
891
|
else {
|
882
892
|
const previousActiveAsset = this._previousActiveModel?.asset as Models.ControlClipModel;
|
883
893
|
if (asset.controlActivation) {
|
884
|
-
const obj = asset.sourceObject as
|
894
|
+
const obj = asset.sourceObject as Object3D;
|
885
895
|
if (previousActiveAsset?.sourceObject !== obj)
|
886
896
|
obj.visible = false;
|
887
897
|
}
|