@needle-tools/engine 2.63.3-pre → 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.
Files changed (100) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/needle-engine.js +9760 -9635
  3. package/dist/needle-engine.umd.cjs +237 -236
  4. package/lib/engine/engine_application.d.ts +7 -1
  5. package/lib/engine/engine_application.js +11 -0
  6. package/lib/engine/engine_application.js.map +1 -1
  7. package/lib/engine/engine_constants.d.ts +1 -1
  8. package/lib/engine/engine_constants.js +1 -1
  9. package/lib/engine/engine_constants.js.map +1 -1
  10. package/lib/engine/engine_gameobject.js +2 -2
  11. package/lib/engine/engine_gameobject.js.map +1 -1
  12. package/lib/engine/engine_instancing.d.ts +1 -0
  13. package/lib/engine/engine_instancing.js +3 -2
  14. package/lib/engine/engine_instancing.js.map +1 -1
  15. package/lib/engine/engine_license.js +14 -13
  16. package/lib/engine/engine_license.js.map +1 -1
  17. package/lib/engine/engine_mainloop_utils.js +27 -52
  18. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  19. package/lib/engine/engine_physics.d.ts +1 -0
  20. package/lib/engine/engine_physics.js +30 -0
  21. package/lib/engine/engine_physics.js.map +1 -1
  22. package/lib/engine/engine_serialization_builtin_serializer.d.ts +1 -0
  23. package/lib/engine/engine_serialization_builtin_serializer.js +30 -25
  24. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  25. package/lib/engine/engine_serialization_core.js +8 -0
  26. package/lib/engine/engine_serialization_core.js.map +1 -1
  27. package/lib/engine/engine_setup.d.ts +1 -1
  28. package/lib/engine/engine_setup.js.map +1 -1
  29. package/lib/engine/engine_time.d.ts +1 -0
  30. package/lib/engine/engine_time.js +5 -1
  31. package/lib/engine/engine_time.js.map +1 -1
  32. package/lib/engine/engine_types.d.ts +12 -0
  33. package/lib/engine/engine_types.js.map +1 -1
  34. package/lib/engine-components/Animation.js +6 -6
  35. package/lib/engine-components/Animation.js.map +1 -1
  36. package/lib/engine-components/AnimatorController.js +2 -0
  37. package/lib/engine-components/AnimatorController.js.map +1 -1
  38. package/lib/engine-components/AudioListener.js +2 -0
  39. package/lib/engine-components/AudioListener.js.map +1 -1
  40. package/lib/engine-components/AudioSource.d.ts +2 -1
  41. package/lib/engine-components/AudioSource.js +19 -4
  42. package/lib/engine-components/AudioSource.js.map +1 -1
  43. package/lib/engine-components/Component.d.ts +1 -0
  44. package/lib/engine-components/Component.js.map +1 -1
  45. package/lib/engine-components/GridHelper.d.ts +1 -0
  46. package/lib/engine-components/GridHelper.js +7 -0
  47. package/lib/engine-components/GridHelper.js.map +1 -1
  48. package/lib/engine-components/ParticleSystem.js +13 -1
  49. package/lib/engine-components/ParticleSystem.js.map +1 -1
  50. package/lib/engine-components/Renderer.d.ts +5 -1
  51. package/lib/engine-components/Renderer.js +78 -29
  52. package/lib/engine-components/Renderer.js.map +1 -1
  53. package/lib/engine-components/WebARSessionRoot.d.ts +1 -0
  54. package/lib/engine-components/WebARSessionRoot.js +10 -0
  55. package/lib/engine-components/WebARSessionRoot.js.map +1 -1
  56. package/lib/engine-components/js-extensions/Object3D.js +11 -1
  57. package/lib/engine-components/js-extensions/Object3D.js.map +1 -1
  58. package/lib/engine-components/timeline/PlayableDirector.d.ts +5 -0
  59. package/lib/engine-components/timeline/PlayableDirector.js +36 -23
  60. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  61. package/lib/engine-components/timeline/TimelineTracks.d.ts +9 -0
  62. package/lib/engine-components/timeline/TimelineTracks.js +121 -21
  63. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  64. package/lib/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +2 -1
  66. package/src/engine/engine_application.ts +14 -3
  67. package/src/engine/engine_constants.ts +1 -1
  68. package/src/engine/engine_gameobject.ts +2 -2
  69. package/src/engine/engine_instancing.ts +3 -3
  70. package/src/engine/engine_license.ts +14 -13
  71. package/src/engine/engine_mainloop_utils.ts +32 -58
  72. package/src/engine/engine_physics.ts +23 -0
  73. package/src/engine/engine_serialization_builtin_serializer.ts +36 -27
  74. package/src/engine/engine_serialization_core.ts +8 -1
  75. package/src/engine/engine_setup.ts +1 -1
  76. package/src/engine/engine_time.ts +9 -4
  77. package/src/engine/engine_types.ts +36 -22
  78. package/src/engine-components/Animation.ts +6 -5
  79. package/src/engine-components/AnimatorController.ts +1 -0
  80. package/src/engine-components/AudioListener.ts +1 -0
  81. package/src/engine-components/AudioSource.ts +20 -6
  82. package/src/engine-components/Component.ts +3 -0
  83. package/src/engine-components/GridHelper.ts +10 -2
  84. package/src/engine-components/ParticleSystem.ts +15 -2
  85. package/src/engine-components/Renderer.ts +92 -33
  86. package/src/engine-components/WebARSessionRoot.ts +14 -0
  87. package/src/engine-components/js-extensions/Object3D.ts +11 -3
  88. package/src/engine-components/timeline/PlayableDirector.ts +35 -24
  89. package/src/engine-components/timeline/TimelineTracks.ts +132 -22
  90. package/src/tsconfig.json +33 -0
  91. package/src/engine/codegen/license.js +0 -1
  92. /package/{src/plugins → plugins}/vite/build.js +0 -0
  93. /package/{src/plugins → plugins}/vite/config.js +0 -0
  94. /package/{src/plugins → plugins}/vite/gzip.js +0 -0
  95. /package/{src/plugins → plugins}/vite/index.js +0 -0
  96. /package/{src/plugins → plugins}/vite/meta.js +0 -0
  97. /package/{src/plugins → plugins}/vite/poster-client.js +0 -0
  98. /package/{src/plugins → plugins}/vite/poster.js +0 -0
  99. /package/{src/plugins → plugins}/vite/reload-client.js +0 -0
  100. /package/{src/plugins → plugins}/vite/reload.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.63.3-pre",
3
+ "version": "2.64.0-pre",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally.",
5
5
  "main": "dist/needle-engine.umd.cjs",
6
6
  "module": "dist/needle-engine.js",
@@ -26,6 +26,7 @@
26
26
  "src",
27
27
  "dist",
28
28
  "lib",
29
+ "plugins",
29
30
  "license-2447137e.js"
30
31
  ],
31
32
  "keywords": [
@@ -3,13 +3,24 @@ import { Context } from "./engine_setup";
3
3
  export enum ApplicationEvents {
4
4
  Visible = "application-visible",
5
5
  Hidden = "application-hidden",
6
+ MuteChanged = "application-mutechanged",
6
7
  }
7
8
 
8
9
  export class Application extends EventTarget {
9
10
 
10
- private context : Context;
11
+ private _mute: boolean = false;
12
+ /** audio muted? */
13
+ get muted() { return this._mute; }
14
+ /** set global audio mute */
15
+ set muted(value) {
16
+ if (value === this._mute) return;
17
+ this._mute = value;
18
+ this.dispatchEvent(new Event(ApplicationEvents.MuteChanged));
19
+ }
20
+
21
+ private context: Context;
11
22
 
12
- public get hasFocus() : boolean {
23
+ public get hasFocus(): boolean {
13
24
  return document.hasFocus();
14
25
  }
15
26
 
@@ -19,7 +30,7 @@ export class Application extends EventTarget {
19
30
 
20
31
  private _isVisible: boolean = true;
21
32
 
22
- constructor(context : Context) {
33
+ constructor(context: Context) {
23
34
  super();
24
35
  this.context = context;
25
36
  // console.log("APP");
@@ -1,3 +1,3 @@
1
1
 
2
- export const activeInHierarchyFieldName = "__isActiveInHierarchy";
2
+ export const activeInHierarchyFieldName = Symbol("isActiveInHierarchy");
3
3
  export const builtinComponentKeyName = "builtin_components";
@@ -6,7 +6,7 @@ import { logHierarchy, setWorldPosition, setWorldQuaternion } from "./engine_thr
6
6
  import { GuidsMap, IComponent as Component, IComponent, IGameObject as GameObject, UIDProvider } from "./engine_types";
7
7
  import { getParam, tryFindObject } from "./engine_utils";
8
8
  import { apply } from "../engine-components/js-extensions/Object3D";
9
- import { InstancingUtil } from "./engine_instancing";
9
+ import { $isUsingInstancing, InstancingUtil } from "./engine_instancing";
10
10
  import { activeInHierarchyFieldName } from "./engine_constants";
11
11
  import { assign } from "./engine_serialization_core";
12
12
 
@@ -75,7 +75,7 @@ export function isActiveInHierarchy(go: Object3D): boolean {
75
75
  }
76
76
 
77
77
  export function markAsInstancedRendered(go: THREE.Object3D, instanced: boolean) {
78
- go["__isUsingInstancing"] = instanced;
78
+ go[$isUsingInstancing] = instanced;
79
79
  }
80
80
 
81
81
  export function isUsingInstancing(instance: THREE.Object3D): boolean { return InstancingUtil.isUsingInstancing(instance); }
@@ -1,17 +1,17 @@
1
1
  export const NEED_UPDATE_INSTANCE_KEY = Symbol("NEEDLE_NEED_UPDATE_INSTANCE");
2
2
 
3
-
3
+ export const $isUsingInstancing = Symbol("isUsingInstancing");
4
4
 
5
5
  export class InstancingUtil {
6
6
 
7
- static isUsingInstancing(instance: THREE.Object3D): boolean { return instance["__isUsingInstancing"] === true; }
7
+ static isUsingInstancing(instance: THREE.Object3D): boolean { return instance[$isUsingInstancing] === true; }
8
8
 
9
9
  // TODO: change this so it does not set matrix world directly but some flag that is only used by instancing
10
10
  static markDirty(go: THREE.Object3D | null, recursive: boolean = true) {
11
11
  if (!go) return;
12
12
  // potential optimization:
13
13
  // if(go.matrixWorldNeedsUpdate) return;
14
- // console.trace(go, GameObject.isUsingInstancing(go));
14
+ // console.warn("UPDATE", go);
15
15
  if (this.isUsingInstancing(go)) {
16
16
  go[NEED_UPDATE_INSTANCE_KEY] = true;
17
17
  go.matrixWorldNeedsUpdate = true;
@@ -42,7 +42,7 @@ async function showLicenseInfo(ctx: IContext) {
42
42
 
43
43
 
44
44
  const _licenseText = "🌵 <span class=\"text\">Made with <a href=\"https://needle.tools\" target=\"_blank\">Needle</a></span>";
45
- const licenseElementClass = "needle-license-element";
45
+ const licenseElementIdentifier = "needle-license-element";
46
46
  const licenseDuration = 30000;
47
47
  const licenseDelay = 600;
48
48
 
@@ -78,6 +78,7 @@ function insertNonCommercialUseHint(ctx: IContext) {
78
78
  setTimeout(() => {
79
79
  clearInterval(interval);
80
80
  licenseElement?.remove();
81
+ style?.remove();
81
82
  }, removeDelay);
82
83
 
83
84
  }
@@ -97,8 +98,7 @@ async function logNonCommercialUse(_logo?: string) {
97
98
 
98
99
  function createLicenseElement() {
99
100
  const licenseElement = document.createElement("div");
100
- licenseElement.classList.add(licenseElementClass);
101
- licenseElement.setAttribute("data-needle_engine_license_element", "");
101
+ licenseElement.setAttribute(licenseElementIdentifier, "");
102
102
  licenseElement.style.position = "fixed";
103
103
  licenseElement.style.bottom = "12px";
104
104
  licenseElement.style.right = "15px";
@@ -112,10 +112,8 @@ function createLicenseElement() {
112
112
  // TODO: would be better to put this into a web element and use shadow dom
113
113
  function createLicenseStyle() {
114
114
  const licenseStyle = document.createElement("style");
115
- const selector = "." + licenseElementClass;
115
+ const selector = `div[${licenseElementIdentifier}]`;
116
116
  licenseStyle.innerHTML = `
117
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap');
118
-
119
117
  ${selector} {
120
118
  font-family: 'Roboto', sans-serif !important;
121
119
  font-weight: 300;
@@ -145,23 +143,26 @@ function createLicenseStyle() {
145
143
  opacity: .9;
146
144
  }
147
145
  2% {
148
- transform: translate(0, 3px);
146
+ transform: translate(0, 2.5px);
149
147
  }
150
- 6% {
148
+ 3% {
151
149
  transform: translate(0, 0px);
152
150
  pointer-events: all;
153
151
  opacity: 1;
154
152
  }
155
- 50% {
153
+ 4% {
156
154
  transform: scale(1)
157
155
  }
158
- 51% {
159
- transform: scale(1.2)
156
+ 4.5% {
157
+ transform: scale(1.3)
158
+ }
159
+ 6% {
160
+ transform: scale(1.32)
160
161
  }
161
- 53% {
162
+ 7% {
162
163
  transform: scale(1)
163
164
  }
164
- 90% {
165
+ 98% {
165
166
  opacity: 1;
166
167
  pointer-events: all;
167
168
  transform: scale(1)
@@ -7,6 +7,7 @@ import { isActiveSelf } from './engine_gameobject';
7
7
  import { ContextRegistry } from "./engine_context_registry";
8
8
 
9
9
  const debug = getParam("debugnewscripts");
10
+ const debugHierarchy = getParam("debughierarchy");
10
11
 
11
12
  // if some other script adds new scripts in onEnable or awake
12
13
  // the original array should be cleared before processing it
@@ -214,8 +215,6 @@ function removeFromArray(script: any, array: any[]) {
214
215
  if (index >= 0) array.splice(index, 1);
215
216
  }
216
217
 
217
- const previousActiveMap: { [key: string]: boolean } = {};
218
- const previousActiveInHierarchyMap: { [key: string]: boolean } = {};
219
218
 
220
219
  export function updateIsActive(obj?: Object3D) {
221
220
  if (!obj) obj = ContextRegistry.Current.scene;
@@ -226,67 +225,43 @@ export function updateIsActive(obj?: Object3D) {
226
225
  updateIsActiveInHierarchyRecursiveRuntime(obj, isActiveSelf(obj), true);
227
226
  }
228
227
 
229
- // const $wasSetVisibleBefore = Symbol("wasSetVisibleBefore");
230
228
 
231
229
  function updateIsActiveInHierarchyRecursiveRuntime(go: THREE.Object3D, activeInHierarchy: boolean, allowEventCall: boolean) {
232
- let activeStateChanged: boolean = false;
233
-
234
- const active = isActiveSelf(go);
235
-
236
- // this is a test if we dont control active state from visibility and set
237
- // active to true by default (even if the object is invisible) in engine_gameobjects:isActiveSelf
238
- // then we need to check if the object is set to visible for the first time
239
- // const visible = go.visible;
240
- // if (!active && visible) {
241
- // if (!go[$wasSetVisibleBefore]) {
242
- // go[$wasSetVisibleBefore] = true;
243
- // setActive(go, true);
244
- // }
245
- // }
246
-
247
- // if (activeInHierarchy) {
248
- // const prevActive = previousActiveMap[go.uuid];
249
- // if (prevActive !== undefined) {
250
- // if (prevActive !== active) {
251
- // activeStateChanged = true;
252
- // if (allowEventCall) {
253
- // perComponent(go, comp => {
254
- // if (active) {
255
- // utils.safeInvoke(comp.__internalAwake.bind(comp));
256
- // comp.onEnable();
257
- // }
258
- // else comp.onDisable();
259
- // });
260
- // }
261
- // }
262
- // }
263
- // }
264
- previousActiveMap[go.uuid] = active;
265
230
 
231
+ if (activeInHierarchy) {
232
+ activeInHierarchy = isActiveSelf(go);
233
+ // IF we update activeInHierarchy within a disabled hierarchy we need to check the parent
234
+ if (activeInHierarchy && go.parent) {
235
+ activeInHierarchy = go.parent[constants.activeInHierarchyFieldName]
236
+ }
237
+ }
266
238
 
267
- if (activeInHierarchy) activeInHierarchy = isActiveSelf(go);
239
+ const prevActive = go[constants.activeInHierarchyFieldName];
240
+ const changed = prevActive !== activeInHierarchy;
268
241
  go[constants.activeInHierarchyFieldName] = activeInHierarchy;
269
242
 
270
243
  // only raise events here if we didnt call enable etc already
271
- if (!activeStateChanged) {
272
- const prevActiveInHierarchy = previousActiveInHierarchyMap[go.uuid];
273
- if (prevActiveInHierarchy !== undefined) {
274
- if (prevActiveInHierarchy !== activeInHierarchy) {
275
- // console.log("CHANGE", go.name, activeInHierarchy);
276
- if (allowEventCall) {
277
- perComponent(go, comp => {
278
- if (activeInHierarchy) {
279
- utils.safeInvoke(comp.__internalAwake.bind(comp));
280
- comp.enabled = true;
281
- // comp.onEnable();
282
- }
283
- else comp.enabled = false;
284
- });
244
+ if (changed) {
245
+ if (debugHierarchy)
246
+ console.warn("ACTIVE CHANGE", go.name, activeInHierarchy, "changed?" + changed, go.userData.components, go.children, go);
247
+ if (allowEventCall) {
248
+ perComponent(go, comp => {
249
+ if (activeInHierarchy) {
250
+ if (comp.enabled) {
251
+ utils.safeInvoke(comp.__internalAwake.bind(comp));
252
+ comp["__didEnable"] = true;
253
+ comp.onEnable();
254
+ }
285
255
  }
286
- }
256
+ else {
257
+ if (comp["__didAwake"]) {
258
+ comp["__didEnable"] = false;
259
+ comp.onDisable();
260
+ }
261
+ }
262
+ });
287
263
  }
288
264
  }
289
- previousActiveInHierarchyMap[go.uuid] = activeInHierarchy;
290
265
 
291
266
  if (go.children) {
292
267
  for (const ch of go.children) {
@@ -312,7 +287,6 @@ export function updateActiveInHierarchyWithoutEventCall(go: THREE.Object3D) {
312
287
  console.error("GO is null");
313
288
  return;
314
289
  }
315
- previousActiveInHierarchyMap[go.uuid] = activeInHierarchy;
316
290
  go[constants.activeInHierarchyFieldName] = activeInHierarchy && foundScene;
317
291
  }
318
292
 
@@ -341,7 +315,7 @@ export function registerPrewarmObject(obj: Object3D, context: IContext) {
341
315
  obj[$waitingForPrewarm] = true;
342
316
  const list = prewarmList.get(context);
343
317
  list!.push(obj);
344
- if(debugPrewarm) console.debug("register prewarm", obj.name);
318
+ if (debugPrewarm) console.debug("register prewarm", obj.name);
345
319
  }
346
320
 
347
321
  let prewarmTarget: WebGLCubeRenderTarget | null = null;
@@ -355,7 +329,7 @@ export function runPrewarm(context: IContext) {
355
329
 
356
330
  const cam = context.mainCamera;
357
331
  if (cam) {
358
- if(debugPrewarm) console.log("prewarm", list.length, "objects", [...list]);
332
+ if (debugPrewarm) console.log("prewarm", list.length, "objects", [...list]);
359
333
  const renderer = context.renderer;
360
334
  const scene = context.scene;
361
335
  renderer.compile(scene, cam!)
@@ -364,10 +338,10 @@ export function runPrewarm(context: IContext) {
364
338
  prewarmCamera.update(renderer, scene);
365
339
  for (const obj of list) {
366
340
  obj[$prewarmedFlag] = true;
367
- obj[$waitingForPrewarm] = false;
341
+ obj[$waitingForPrewarm] = false;
368
342
  }
369
343
  list.length = 0;
370
- if(debugPrewarm) console.log("prewarm done");
344
+ if (debugPrewarm) console.log("prewarm done");
371
345
  }
372
346
  }
373
347
 
@@ -26,6 +26,7 @@ const debugPhysics = getParam("debugphysics");
26
26
  const debugColliderPlacement = getParam("debugphysicscolliders");
27
27
  const debugCollisions = getParam("debugcollisions");
28
28
  const showColliders = getParam("showcolliders");
29
+ const noPhysics = getParam("nophysics");
29
30
 
30
31
 
31
32
  declare type PhysicsBody = {
@@ -286,6 +287,8 @@ export class Physics {
286
287
 
287
288
  // physics simulation
288
289
 
290
+ enabled: boolean = true;
291
+
289
292
  private _tempPosition: Vector3 = new Vector3();
290
293
  private _tempQuaternion: Quaternion = new Quaternion();
291
294
  private _tempScale: Vector3 = new Vector3();
@@ -325,6 +328,7 @@ export class Physics {
325
328
  Physics._didLoadPhysicsEngine = true;
326
329
  }
327
330
  this.world = new World(this._gravity);
331
+ if (noPhysics) this.enabled = false;
328
332
  }
329
333
 
330
334
  private _gravity = { x: 0.0, y: -9.81, z: 0.0 };
@@ -347,6 +351,10 @@ export class Physics {
347
351
  }
348
352
 
349
353
  addBoxCollider(collider: ICollider, center: Vector3, size: Vector3) {
354
+ if (!this.enabled) {
355
+ if(debugPhysics) console.warn("Physics is disabled");
356
+ return;
357
+ }
350
358
  const obj = collider.gameObject;
351
359
  const scale = getWorldScale(obj, this._tempPosition).multiply(size);
352
360
  scale.multiplyScalar(0.5);
@@ -355,6 +363,10 @@ export class Physics {
355
363
  }
356
364
 
357
365
  addSphereCollider(collider: ICollider, center: Vector3, radius: number) {
366
+ if (!this.enabled) {
367
+ if(debugPhysics) console.warn("Physics is disabled");
368
+ return;
369
+ }
358
370
  const obj = collider.gameObject;
359
371
  const scale = getWorldScale(obj, this._tempPosition).multiplyScalar(radius);
360
372
  const desc = ColliderDesc.ball(scale.x);
@@ -362,6 +374,10 @@ export class Physics {
362
374
  }
363
375
 
364
376
  addCapsuleCollider(collider: ICollider, center: Vector3, height: number, radius: number) {
377
+ if (!this.enabled) {
378
+ if(debugPhysics) console.warn("Physics is disabled");
379
+ return;
380
+ }
365
381
  const obj = collider.gameObject;
366
382
  const scale = getWorldScale(obj, this._tempPosition);
367
383
  if (debugPhysics) console.log("capsule scale", scale, height, radius);
@@ -370,6 +386,10 @@ export class Physics {
370
386
  }
371
387
 
372
388
  addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, scale: Vector3) {
389
+ if (!this.enabled) {
390
+ if(debugPhysics) console.warn("Physics is disabled");
391
+ return;
392
+ }
373
393
  const geo = mesh.geometry;
374
394
  if (!geo) {
375
395
  if (debugPhysics) console.warn("Missing mesh geometry", mesh.name);
@@ -575,6 +595,7 @@ export class Physics {
575
595
  }
576
596
 
577
597
  updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean) {
598
+ if (!this.enabled) return;
578
599
  if (comp.destroyed || !comp.gameObject) return;
579
600
  if (!translation && !rotation) return;
580
601
 
@@ -637,6 +658,7 @@ export class Physics {
637
658
 
638
659
  public step(dt?: number) {
639
660
  if (!this.world) return;
661
+ if (!this.enabled) return;
640
662
  this._isUpdatingPhysicsWorld = true;
641
663
  if (!this.eventQueue) {
642
664
  this.eventQueue = new EventQueue(false);
@@ -670,6 +692,7 @@ export class Physics {
670
692
 
671
693
  public postStep() {
672
694
  if (!this.world) return;
695
+ if (!this.enabled) return;
673
696
  this._isUpdatingPhysicsWorld = true;
674
697
  this.syncObjects();
675
698
  this._isUpdatingPhysicsWorld = false;
@@ -206,6 +206,8 @@ declare type EventListCall = {
206
206
  enabled?: boolean,
207
207
  }
208
208
 
209
+ const $eventListDebugInfo = Symbol("eventListDebugInfo");
210
+
209
211
  class EventListSerializer extends TypeSerializer {
210
212
  constructor() {
211
213
  super([EventList]);
@@ -238,13 +240,18 @@ class EventListSerializer extends TypeSerializer {
238
240
  const hasMethod = call.method?.length > 0;
239
241
  if (target && hasMethod) {
240
242
  const printWarningMethodNotFound = () => console.warn(`Could not find method ${call.method} on object ${target.name}`, target, typeof target[call.method]);
241
- if (typeof target[call.method] !== "function") {
243
+ const method = target[call.method];
244
+ if (typeof method !== "function") {
242
245
  let foundMethod = false;
246
+ let currentPrototype = target;
243
247
  // test if the target method is actually a property setter
244
- // in which case we want to remove the leading set prefix and see if the method or property exists
245
- if (call.method.startsWith("set_") && call.method.length > 4) {
246
- call.method = call.method.substring(4);
247
- if (target[call.method] !== undefined) foundMethod = true;
248
+ while (currentPrototype) {
249
+ const desc = Object.getOwnPropertyDescriptor(currentPrototype, call.method);
250
+ if (desc && (desc.writable === true || desc.set)) {
251
+ foundMethod = true;
252
+ break;
253
+ }
254
+ currentPrototype = Object.getPrototypeOf(currentPrototype);
248
255
  }
249
256
  if (!foundMethod && (isDevEnvironment() || debugExtension))
250
257
  printWarningMethodNotFound();
@@ -252,38 +259,23 @@ class EventListSerializer extends TypeSerializer {
252
259
  }
253
260
  let fn: CallInfo | undefined;
254
261
  let args = call.argument;
262
+
263
+ // This is the final method we pass to the call info (or undefined if the method couldnt be resolved)
264
+ const eventMethod = hasMethod ? this.createEventMethod(target, call.method, args) : undefined;
265
+
255
266
  if (call.argument !== undefined) {
256
267
  if (typeof args === "object") {
257
268
  args = objectSerializer.onDeserialize(call.argument, context);
258
269
  if (!args) args = componentSerializer.onDeserialize(call.argument, context);
259
270
  }
260
- fn = new CallInfo(hasMethod ? (...args) => invokeFunction(...args) : undefined, call.enabled);
271
+ fn = new CallInfo(eventMethod, call.enabled);
261
272
  }
262
273
  else
263
- fn = new CallInfo(hasMethod ? (...args) => invokeFunction(...args) : undefined, call.enabled);
274
+ fn = new CallInfo(eventMethod, call.enabled);
264
275
 
265
276
 
266
- // TODO: move this into the event list directly
267
- // this scope should not stay in the serializer.
268
- // the event list should be able to modify the args that were set in unity if necessary
269
- // and pass them on to the component
270
- const invokeFunction = (...forwardedArgs) => {
271
- const method = target[call.method];
272
-
273
- if (typeof method === "function") {
274
- if (args !== undefined)
275
- method?.call(target, args);
276
- else // support invoking EventList with any number of arguments (if none were declared in unity)
277
- method?.call(target, ...forwardedArgs);
278
- }
279
- else // the target "method" can be a property too
280
- {
281
- target[call.method] = args;
282
- }
283
- };
284
-
285
277
  if (!fn.method)
286
- fn["__debuginfo"] = context.object?.name;
278
+ fn[$eventListDebugInfo] = context.object?.name;
287
279
  if (!target || !fn.method) {
288
280
  const debugInfo = context.object ? "Current object: " + context.object.name + ", " + context.object["guid"] : null;
289
281
  if (!target)
@@ -307,6 +299,23 @@ class EventListSerializer extends TypeSerializer {
307
299
  }
308
300
  return undefined;
309
301
  }
302
+
303
+ private createEventMethod(target : object, methodName: string, args?: any) : Function | undefined {
304
+
305
+ return (...forwardedArgs) => {
306
+ const method = target[methodName];
307
+ if (typeof method === "function") {
308
+ if (args !== undefined)
309
+ method?.call(target, args);
310
+ else // support invoking EventList with any number of arguments (if none were declared in unity)
311
+ method?.call(target, ...forwardedArgs);
312
+ }
313
+ else // the target "method" can be a property too
314
+ {
315
+ target[methodName] = args;
316
+ }
317
+ };
318
+ }
310
319
  }
311
320
  export const eventListSerializer = new EventListSerializer();
312
321
 
@@ -1,6 +1,6 @@
1
1
  import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
2
2
  import { getParam } from "./engine_utils";
3
- import { Object3D } from "three";
3
+ import { AnimationClip, Material, Mesh, Object3D, Texture } from "three";
4
4
  import { Context } from "./engine_setup";
5
5
  import { isPersistentAsset } from "./extensions/NEEDLE_persistent_assets";
6
6
  import { SourceIdentifier } from "./engine_types";
@@ -545,6 +545,13 @@ function deserializeObjectWithType(data: any, typeOrConstructor: Constructor<any
545
545
  else {
546
546
  // happens when exporting e.g. Animation component with only clip assigned (clips array is marked as serialized but it might be undefined if no clips are assigned in e.g. blender)
547
547
  if (data === undefined) return undefined;
548
+ if (data === null) {
549
+ // Dont implictly create instances for three types that are not assigned
550
+ // see: https://github.com/needle-tools/needle-tiny/issues/637
551
+ if (type === Material || type === Texture || type === Mesh || type === AnimationClip) {
552
+ return null;
553
+ }
554
+ }
548
555
  // the fallback - this assumes that the type has a constructor that accepts the serialized arguments
549
556
  // made originally with THREE.Vector3 in mind but SHOULD actually not be used/called anymore
550
557
  instance = new type(...setBuffer(data));
@@ -233,7 +233,7 @@ export class Context implements IContext {
233
233
 
234
234
  private _stats: Stats.default | null = stats ? Stats.default() : null;
235
235
 
236
- constructor(args: ContextArgs | undefined) {
236
+ constructor(args?: ContextArgs) {
237
237
  this.name = args?.name || "";
238
238
  this.alias = args?.alias;
239
239
  this.domElement = args?.domElement || document.body;
@@ -3,7 +3,7 @@ import { getParam } from './engine_utils';
3
3
 
4
4
  const timescaleUrl = getParam("timescale");
5
5
  let timeScale = 1;
6
- if(typeof timescaleUrl === "number") timeScale = timescaleUrl;
6
+ if (typeof timescaleUrl === "number") timeScale = timescaleUrl;
7
7
 
8
8
  export class Time {
9
9
 
@@ -28,12 +28,17 @@ export class Time {
28
28
  private _fpsSamples: number[] = [];
29
29
  private _fpsSampleIndex: number = 0;
30
30
 
31
+ constructor() {
32
+ if (typeof timeScale === "number")
33
+ this.timeScale = timeScale;
34
+ }
35
+
31
36
  update() {
32
37
  this.deltaTime = this.clock.getDelta();
33
38
  // clamp delta time because if tab is not active clock.getDelta can get pretty big
34
39
  this.deltaTime = Math.min(.1, this.deltaTime);
35
- this.deltaTime *= timeScale * this.timeScale;
36
- if(this.deltaTime <= 0) this.deltaTime = 0.000000000001;
40
+ this.deltaTime *= this.timeScale;
41
+ if (this.deltaTime <= 0) this.deltaTime = 0.000000000001;
37
42
  this.frame += 1;
38
43
  this.time += this.deltaTime;
39
44
 
@@ -42,6 +47,6 @@ export class Time {
42
47
  let sum = 0;
43
48
  for (let i = 0; i < this._fpsSamples.length; i++)
44
49
  sum += this._fpsSamples[i];
45
- this._smoothedFps = 1/(sum / this._fpsSamples.length);
50
+ this._smoothedFps = 1 / (sum / this._fpsSamples.length);
46
51
  }
47
52
  }