@needle-tools/engine 2.58.4-pre → 2.59.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 (68) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/needle-engine.js +8820 -8736
  3. package/dist/needle-engine.umd.cjs +195 -195
  4. package/lib/engine/debug/debug_overlay.js +11 -0
  5. package/lib/engine/debug/debug_overlay.js.map +1 -1
  6. package/lib/engine/engine_addressables.js +8 -5
  7. package/lib/engine/engine_addressables.js.map +1 -1
  8. package/lib/engine/engine_element.js +2 -1
  9. package/lib/engine/engine_element.js.map +1 -1
  10. package/lib/engine/engine_gameobject.d.ts +3 -1
  11. package/lib/engine/engine_gameobject.js +31 -2
  12. package/lib/engine/engine_gameobject.js.map +1 -1
  13. package/lib/engine/engine_hot_reload.d.ts +2 -0
  14. package/lib/engine/engine_hot_reload.js +15 -2
  15. package/lib/engine/engine_hot_reload.js.map +1 -1
  16. package/lib/engine/engine_input.d.ts +2 -0
  17. package/lib/engine/engine_input.js +16 -0
  18. package/lib/engine/engine_input.js.map +1 -1
  19. package/lib/engine/engine_utils.d.ts +1 -1
  20. package/lib/engine/engine_utils.js +5 -2
  21. package/lib/engine/engine_utils.js.map +1 -1
  22. package/lib/engine/extensions/NEEDLE_gameobject_data.js +6 -4
  23. package/lib/engine/extensions/NEEDLE_gameobject_data.js.map +1 -1
  24. package/lib/engine-components/Animator.js +10 -6
  25. package/lib/engine-components/Animator.js.map +1 -1
  26. package/lib/engine-components/AnimatorController.d.ts +1 -1
  27. package/lib/engine-components/AnimatorController.js +19 -12
  28. package/lib/engine-components/AnimatorController.js.map +1 -1
  29. package/lib/engine-components/Component.d.ts +1 -0
  30. package/lib/engine-components/Component.js +4 -1
  31. package/lib/engine-components/Component.js.map +1 -1
  32. package/lib/engine-components/OrbitControls.d.ts +7 -2
  33. package/lib/engine-components/OrbitControls.js +34 -15
  34. package/lib/engine-components/OrbitControls.js.map +1 -1
  35. package/lib/engine-components/ParticleSystem.d.ts +1 -0
  36. package/lib/engine-components/ParticleSystem.js +15 -2
  37. package/lib/engine-components/ParticleSystem.js.map +1 -1
  38. package/lib/engine-components/WebXR.js.map +1 -1
  39. package/lib/engine-components/timeline/PlayableDirector.js +5 -2
  40. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  41. package/lib/engine-components/timeline/TimelineTracks.d.ts +1 -0
  42. package/lib/engine-components/timeline/TimelineTracks.js +14 -3
  43. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  44. package/lib/engine-components/ui/EventSystem.d.ts +10 -5
  45. package/lib/engine-components/ui/EventSystem.js +45 -41
  46. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  47. package/lib/engine-components/ui/Raycaster.js +2 -2
  48. package/lib/engine-components/ui/Raycaster.js.map +1 -1
  49. package/lib/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +3 -3
  51. package/src/engine/debug/debug_overlay.ts +17 -5
  52. package/src/engine/engine_addressables.ts +8 -5
  53. package/src/engine/engine_element.ts +2 -1
  54. package/src/engine/engine_gameobject.ts +34 -3
  55. package/src/engine/engine_hot_reload.ts +16 -5
  56. package/src/engine/engine_input.ts +11 -0
  57. package/src/engine/engine_utils.ts +4 -3
  58. package/src/engine/extensions/NEEDLE_gameobject_data.ts +11 -6
  59. package/src/engine-components/Animator.ts +12 -9
  60. package/src/engine-components/AnimatorController.ts +20 -13
  61. package/src/engine-components/Component.ts +5 -1
  62. package/src/engine-components/OrbitControls.ts +44 -19
  63. package/src/engine-components/ParticleSystem.ts +18 -3
  64. package/src/engine-components/WebXR.ts +1 -0
  65. package/src/engine-components/timeline/PlayableDirector.ts +4 -2
  66. package/src/engine-components/timeline/TimelineTracks.ts +15 -4
  67. package/src/engine-components/ui/EventSystem.ts +48 -42
  68. package/src/engine-components/ui/Raycaster.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.58.4-pre",
3
+ "version": "2.59.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",
@@ -47,7 +47,7 @@
47
47
  "peerjs": "1.3.2",
48
48
  "simplex-noise": "^4.0.1",
49
49
  "stats.js": "^0.17.0",
50
- "three": "npm:@needle-tools/three@^0.146.3",
50
+ "three": "npm:@needle-tools/three@^0.146.4",
51
51
  "three-mesh-ui": "^6.4.5",
52
52
  "three.quarks": "^0.7.3",
53
53
  "uuid": "^9.0.0",
@@ -56,7 +56,7 @@
56
56
  "devDependencies": {
57
57
  "@babel/runtime": "^7.16.0",
58
58
  "@luncheon/esbuild-plugin-gzip": "^0.1.0",
59
- "@needle-tools/gltf-transform-extensions": "^0.10.6-pre.1",
59
+ "@needle-tools/gltf-transform-extensions": "^0.10.8-pre",
60
60
  "@needle-tools/needle-component-compiler": "1.9.3",
61
61
  "@needle-tools/helper": "^0.2.1-pre",
62
62
  "@types/three": "0.146.0",
@@ -30,6 +30,7 @@ export function makeErrorsVisibleForDevelopment() {
30
30
  const error = console.error;
31
31
  console.error = (...args: any[]) => {
32
32
  error.apply(console, args);
33
+ onParseError(args);
33
34
  addLog(LogType.Error, args, null, null);
34
35
  onReceivedError();
35
36
  };
@@ -48,20 +49,31 @@ export function makeErrorsVisibleForDevelopment() {
48
49
 
49
50
  let errorCount = 0;
50
51
 
51
- function onReceivedError(){
52
+ function onReceivedError() {
52
53
  errorCount += 1;
53
54
  }
54
55
 
56
+ function onParseError(args: Array<any>) {
57
+ if (Array.isArray(args)) {
58
+ for (let i = 0; i < args.length; i++) {
59
+ const arg = args[i];
60
+ if (typeof arg === "string" && arg.startsWith("THREE.PropertyBinding: Trying to update node for track:")) {
61
+ args[i] = "Some animated objects couldn't be found: see console for details";
62
+ }
63
+ }
64
+ }
65
+ }
66
+
55
67
  export function addLog(type: LogType, message: string | any[], _file?: string | null, _line?: number | null) {
56
- if(hide) return;
68
+ if (hide) return;
57
69
  const context = ContextRegistry.Current;
58
70
  const domElement = context?.domElement ?? document.querySelector("needle-engine");
59
71
  if (!domElement) return;
60
72
  if (Array.isArray(message)) {
61
73
  let newMessage = "";
62
- for(let i = 0; i < message.length; i++) {
63
- if(typeof message[i] === "object") continue;
64
- if(i > 0) newMessage += " ";
74
+ for (let i = 0; i < message.length; i++) {
75
+ if (typeof message[i] === "object") continue;
76
+ if (i > 0) newMessage += " ";
65
77
  newMessage += message[i];
66
78
  }
67
79
  message = newMessage;
@@ -8,7 +8,7 @@ import { registerPrefabProvider, syncInstantiate } from "./engine_networking_ins
8
8
  import { download, hash } from "./engine_web_api";
9
9
  import { getLoader } from "./engine_gltf";
10
10
  import { SourceIdentifier } from "./engine_types";
11
- import { destroy, instantiate, InstantiateOptions } from "./engine_gameobject";
11
+ import { destroy, instantiate, InstantiateOptions, isDestroyed } from "./engine_gameobject";
12
12
  import { IGameObject } from "./engine_types";
13
13
 
14
14
  const debug = getParam("debugaddressables");
@@ -111,7 +111,7 @@ export class AssetReference {
111
111
  }
112
112
 
113
113
  private get mustLoad() {
114
- return !this.asset || this.asset.__destroyed === true;
114
+ return !this.asset || this.asset.__destroyed === true || isDestroyed(this.asset) === true;
115
115
  }
116
116
 
117
117
  isLoaded() { return this._rawBinary || this.asset !== undefined }
@@ -121,10 +121,11 @@ export class AssetReference {
121
121
  if (debug) console.log("Unload", this.asset);
122
122
  // TODO: we need a way to remove objects from the context (including components) without actually "destroying" them
123
123
  if (this.asset.scene)
124
- destroy(this.asset.scene);
125
- else destroy(this.asset);
124
+ destroy(this.asset.scene, true);
125
+ else destroy(this.asset, true);
126
126
  }
127
127
  this.asset = null;
128
+ this._rawBinary = undefined;
128
129
  }
129
130
 
130
131
  async preload(): Promise<ArrayBuffer | null> {
@@ -142,7 +143,9 @@ export class AssetReference {
142
143
  }
143
144
 
144
145
  async loadAssetAsync(prog?: ProgressCallback | null) {
145
- if (!this.mustLoad) return;
146
+ if (debug)
147
+ console.log("loadAssetAsync", this.uri);
148
+ if (!this.mustLoad) return this.asset;
146
149
  if (prog)
147
150
  this._progressListeners.push(prog);
148
151
  if (this._loading !== undefined) {
@@ -404,7 +404,8 @@ export class EngineElement extends HTMLElement implements INeedleEngineComponent
404
404
  }
405
405
  }
406
406
 
407
- window.customElements.define(htmlTagName, EngineElement);
407
+ if (!window.customElements.get(htmlTagName))
408
+ window.customElements.define(htmlTagName, EngineElement);
408
409
 
409
410
 
410
411
 
@@ -51,8 +51,8 @@ export class InstantiateOptions {
51
51
  // main.processStart(Context.Current, go);
52
52
  // }
53
53
 
54
- const $isActive = Symbol("isActive");
55
54
 
55
+ const $isActive = Symbol("isActive");
56
56
  export function isActiveSelf(go: Object3D): boolean {
57
57
  const visible = go.visible || isUsingInstancing(go);
58
58
  if (go[$isActive] === undefined) {
@@ -86,7 +86,19 @@ export function findByGuid(guid: string, hierarchy: THREE.Object3D): GameObject
86
86
  }
87
87
 
88
88
 
89
- export function destroy(instance: Object3D | Component, recursive: boolean = true, isRoot: boolean = true) {
89
+ const $isDestroyed = Symbol("isDestroyed");
90
+ export function isDestroyed(go: Object3D): boolean {
91
+ return go[$isDestroyed];
92
+ }
93
+ export function setDestroyed(go: Object3D, value: boolean) {
94
+ go[$isDestroyed] = value;
95
+ }
96
+
97
+ export function destroy(instance: Object3D | Component, recursive: boolean = true, dispose: boolean = false) {
98
+ internalDestroy(instance, recursive, dispose, true);
99
+ }
100
+
101
+ function internalDestroy(instance: Object3D | Component, recursive: boolean = true, dispose: boolean = false, isRoot: boolean = true) {
90
102
  const comp = instance as Component;
91
103
  if (comp.isComponent) {
92
104
  comp.__internalDisable();
@@ -94,12 +106,17 @@ export function destroy(instance: Object3D | Component, recursive: boolean = tru
94
106
  return;
95
107
  }
96
108
 
109
+
97
110
  const obj = instance as GameObject;
111
+ if (dispose) disposeObject(obj);
112
+ setDestroyed(obj, true);
113
+
114
+
98
115
  if (debug) console.log(obj);
99
116
 
100
117
  if (recursive && obj.children) {
101
118
  for (const ch of obj.children) {
102
- destroy(ch, recursive, false);
119
+ internalDestroy(ch, recursive, false, false);
103
120
  }
104
121
  }
105
122
 
@@ -125,6 +142,20 @@ export function destroy(instance: Object3D | Component, recursive: boolean = tru
125
142
  obj.removeFromParent();
126
143
  }
127
144
 
145
+ function disposeObject(obj: Object3D) {
146
+ if (!obj) return;
147
+ const mesh = obj as THREE.Mesh;
148
+ if (mesh.geometry) {
149
+ mesh.geometry.dispose();
150
+ }
151
+ if (mesh.material) {
152
+ if (Array.isArray(mesh.material)) {
153
+ mesh.material.forEach(m => m.dispose());
154
+ } else {
155
+ mesh.material.dispose();
156
+ }
157
+ }
158
+ }
128
159
 
129
160
  export function foreachComponent(instance: THREE.Object3D, cb: (comp: Component) => any, recursive: boolean = true): any {
130
161
  if (!instance) return;
@@ -3,6 +3,7 @@ import { TypeStore } from "./engine_typestore";
3
3
  import { addScriptToArrays, removeScriptFromContext } from "./engine_mainloop_utils"
4
4
  import { showBalloonWarning } from "./debug/debug";
5
5
  import { getParam } from "./engine_utils";
6
+ import { addLog, LogType } from "./debug/debug_overlay";
6
7
 
7
8
  const debug = getParam("debughotreload");
8
9
 
@@ -27,6 +28,11 @@ let isApplyingChanges = false;
27
28
 
28
29
  const instances: Map<string, object[]> = new Map();
29
30
 
31
+ /** true during hot reload, can be used to modify behaviour in onEnable and onDisable */
32
+ export function isHotReloading() {
33
+ return isApplyingChanges;
34
+ }
35
+
30
36
  export function register(instance: object) {
31
37
  if (isApplyingChanges) return;
32
38
  const type = instance.constructor;
@@ -77,9 +83,10 @@ function reloadPageOnHotReloadError() {
77
83
 
78
84
  export function applyChanges(newModule): boolean {
79
85
 
80
- reloadPageOnHotReloadError();
86
+ if (debug)
87
+ console.log("Hot Reload - apply changes");
81
88
 
82
- // showBalloonMessage("Hot reloading");
89
+ reloadPageOnHotReloadError();
83
90
 
84
91
  // console.dir(newModule);
85
92
 
@@ -88,14 +95,17 @@ export function applyChanges(newModule): boolean {
88
95
  isApplyingChanges = true;
89
96
 
90
97
  const typeToUpdate = TypeStore.get(key);
91
- if (!typeToUpdate)
92
- {
98
+ if (!typeToUpdate) {
93
99
  continue;
94
100
  }
95
101
  const newType = newModule[key];
96
102
  const instancesOfType = instances.get(newType.name);
97
103
 
98
- console.log("[Needle Engine] Updating type: " + key + " x" + instancesOfType?.length + "");
104
+ let hotReloadMessage = "[Needle Engine] Updating type: " + key;
105
+ let typesCount = instancesOfType?.length ?? -1;
106
+ if (typesCount > 0) hotReloadMessage += " x" + typesCount;
107
+ else hotReloadMessage += " - no instances";
108
+ console.log(hotReloadMessage);
99
109
 
100
110
  // Update prototype (methods and properties)
101
111
  const previousMethods = Object.getOwnPropertyNames(typeToUpdate.prototype);
@@ -177,6 +187,7 @@ export function applyChanges(newModule): boolean {
177
187
  }
178
188
  finally {
179
189
  isApplyingChanges = false;
190
+ addLog(LogType.Log, "Script changes applied (HMR)")
180
191
  }
181
192
  }
182
193
 
@@ -80,6 +80,7 @@ export class Input extends EventTarget {
80
80
  this.context.domElement.style.cursor = "default";
81
81
  }
82
82
 
83
+ /** how many pointers are currently pressed */
83
84
  getPointerPressedCount(): number {
84
85
  let count = 0;
85
86
  for (let i = 0; i < this._pointerPressed.length; i++) {
@@ -340,6 +341,13 @@ export class Input extends EventTarget {
340
341
  this._mouseWheelDeltaY[i] = 0;
341
342
  }
342
343
 
344
+ private canReceiveInput(evt : Event) {
345
+ // If the user has HTML objects ontop of the canvas
346
+ if(evt.target instanceof HTMLCanvasElement) return true;
347
+ const css = window.getComputedStyle(evt.target as HTMLElement);
348
+ if(css.pointerEvents === "all") return false;
349
+ return true;
350
+ }
343
351
 
344
352
  private keysPressed: { [key: number]: { pressed: boolean, frame: number, startFrame: number, key: string } } = {};
345
353
 
@@ -372,6 +380,7 @@ export class Input extends EventTarget {
372
380
  }
373
381
 
374
382
  private onMouseWheel(evt: WheelEvent) {
383
+ if(this.canReceiveInput(evt) === false) return;
375
384
  if (this._mouseWheelDeltaY.length <= 0) this._mouseWheelDeltaY.push(0);
376
385
  if (this._mouseWheelChanged.length <= 0) this._mouseWheelChanged.push(false);
377
386
  this._mouseWheelChanged[0] = true;
@@ -381,6 +390,7 @@ export class Input extends EventTarget {
381
390
 
382
391
  private onTouchStart(evt: TouchEvent) {
383
392
  if (evt.changedTouches.length <= 0) return;
393
+ if(this.canReceiveInput(evt) === false) return;
384
394
  for (let i = 0; i < evt.changedTouches.length; i++) {
385
395
  const touch = evt.changedTouches[i];
386
396
  const id = this.getPointerIndex(touch.identifier)
@@ -415,6 +425,7 @@ export class Input extends EventTarget {
415
425
 
416
426
  private onMouseDown(evt: MouseEvent) {
417
427
  if (evt.defaultPrevented) return;
428
+ if(this.canReceiveInput(evt) === false) return;
418
429
  let i = evt.button;
419
430
  this.onDown({ button: i, clientX: evt.clientX, clientY: evt.clientY, pointerType: PointerType.Mouse, source: evt });
420
431
  }
@@ -80,12 +80,13 @@ export function setParam(paramName: string, paramValue: string): void {
80
80
  document.location.search = urlParams.toString();
81
81
  }
82
82
 
83
- export function setParamWithoutReload(paramName: string, paramValue: string, appendHistory = true): void {
83
+ export function setParamWithoutReload(paramName: string, paramValue: string | null, appendHistory = true): void {
84
84
  const urlParams = getUrlParams();
85
85
  if (urlParams.has(paramName)) {
86
- urlParams.set(paramName, paramValue);
86
+ if(paramValue === null) urlParams.delete(paramName);
87
+ else urlParams.set(paramName, paramValue);
87
88
  }
88
- else
89
+ else if(paramValue !== null)
89
90
  urlParams.append(paramName, paramValue);
90
91
  if (appendHistory) pushState(paramName, urlParams);
91
92
  else setState(paramName, urlParams);
@@ -46,8 +46,7 @@ export class NEEDLE_gameobject_data implements GLTFLoaderPlugin {
46
46
  const node = this.parser.json.nodes[index];
47
47
  if (node && node.extensions) {
48
48
  const ext = node.extensions[EXTENSION_NAME];
49
- if (ext)
50
- {
49
+ if (ext) {
51
50
  const p = this.findAndApplyExtensionData(index, ext);
52
51
  promises.push(p);
53
52
  }
@@ -65,13 +64,19 @@ export class NEEDLE_gameobject_data implements GLTFLoaderPlugin {
65
64
 
66
65
 
67
66
  private applyExtensionData(node: Object3D, ext: GameObjectData) {
67
+ if (ext.layers === undefined) ext.layers = 0;
68
68
  node.userData.layer = ext.layers;
69
69
  node.layers.disableAll();
70
70
  node.layers.set(ext.layers);
71
- node.userData.tag = ext.tag;
72
- node.userData.hideFlags = ext.hideFlags;
73
- node.userData.static = ext.static;
74
- node.visible = ext.activeSelf;
71
+
72
+ node.userData.tag = ext.tag ?? "none";
73
+
74
+ node.userData.hideFlags = ext.hideFlags ?? 0;
75
+
76
+ node.userData.static = ext.static ?? false;
77
+
78
+ node.visible = ext.activeSelf ?? true;
79
+
75
80
  node["guid"] = ext.guid;
76
81
  // console.log(node.name, ext.activeSelf, node);
77
82
  }
@@ -4,7 +4,7 @@ import { LoopOnce, AnimationActionLoopStyles, AnimationAction } from "three";
4
4
  import { getParam, deepClone } from "../engine/engine_utils";
5
5
  import { AnimatorControllerModel } from "../engine/extensions/NEEDLE_animator_controller_model";
6
6
  import { AnimatorController } from "./AnimatorController";
7
- import { serializable } from "../engine/engine_serialization_decorator";
7
+ import { serializable } from "../engine/engine_serialization_decorator";
8
8
  import { Mathf } from "../engine/engine_math";
9
9
 
10
10
  const debug = getParam("debuganimator");
@@ -39,7 +39,7 @@ export class Animator extends Behaviour {
39
39
  }
40
40
  if (val) {
41
41
  if (!(val instanceof AnimatorController)) {
42
- if(debug) console.log("Assign animator controller", val, this);
42
+ if (debug) console.log("Assign animator controller", val, this);
43
43
  this._animatorController = new AnimatorController(val);
44
44
  }
45
45
  else this._animatorController = val;
@@ -50,7 +50,7 @@ export class Animator extends Behaviour {
50
50
  return this._animatorController;
51
51
  }
52
52
 
53
- Play(name: string | number, layer: number = -1, normalizedTime: number = Number.NEGATIVE_INFINITY, transitionDurationInSec:number = 0) {
53
+ Play(name: string | number, layer: number = -1, normalizedTime: number = Number.NEGATIVE_INFINITY, transitionDurationInSec: number = 0) {
54
54
  this.runtimeAnimatorController?.Play(name, layer, normalizedTime, transitionDurationInSec);
55
55
  }
56
56
 
@@ -125,12 +125,15 @@ export class Animator extends Behaviour {
125
125
  if (!this.gameObject) return;
126
126
  if (this.runtimeAnimatorController) {
127
127
  const clone = this.runtimeAnimatorController.clone();
128
- console.assert(this.runtimeAnimatorController !== clone);
129
- this.runtimeAnimatorController = clone;
130
- console.assert(this.runtimeAnimatorController === clone);
131
- this.runtimeAnimatorController.bind(this);
132
- this.runtimeAnimatorController.SetSpeed(this.speed);
133
- this.runtimeAnimatorController.normalizedStartOffset = this.normalizedStartOffset;
128
+ if (clone) {
129
+ console.assert(this.runtimeAnimatorController !== clone);
130
+ this.runtimeAnimatorController = clone;
131
+ console.assert(this.runtimeAnimatorController === clone);
132
+ this.runtimeAnimatorController.bind(this);
133
+ this.runtimeAnimatorController.SetSpeed(this.speed);
134
+ this.runtimeAnimatorController.normalizedStartOffset = this.normalizedStartOffset;
135
+ }
136
+ else console.warn("Could not clone animator controller", this.runtimeAnimatorController);
134
137
  }
135
138
  }
136
139
 
@@ -39,44 +39,44 @@ export class AnimatorController {
39
39
 
40
40
  SetBool(name: string | number, value: boolean) {
41
41
  const key = typeof name === "string" ? "name" : "hash";
42
- return this.model?.parameters.filter(p => p[key] === name).forEach(p => p.value = value);
42
+ return this.model?.parameters?.filter(p => p[key] === name).forEach(p => p.value = value);
43
43
  }
44
44
 
45
45
  GetBool(name: string | number): boolean {
46
46
  const key = typeof name === "string" ? "name" : "hash";
47
- return this.model?.parameters.find(p => p[key] === name)?.value as boolean ?? false;
47
+ return this.model?.parameters?.find(p => p[key] === name)?.value as boolean ?? false;
48
48
  }
49
49
 
50
50
  SetFloat(name: string | number, val: number) {
51
51
  const key = typeof name === "string" ? "name" : "hash";
52
- return this.model?.parameters.filter(p => p[key] === name).forEach(p => p.value = val);
52
+ return this.model?.parameters?.filter(p => p[key] === name).forEach(p => p.value = val);
53
53
  }
54
54
 
55
55
  GetFloat(name: string | number): number {
56
56
  const key = typeof name === "string" ? "name" : "hash";
57
- return this.model?.parameters.find(p => p[key] === name)?.value as number ?? 0;
57
+ return this.model?.parameters?.find(p => p[key] === name)?.value as number ?? 0;
58
58
  }
59
59
 
60
60
  SetInteger(name: string | number, val: number) {
61
61
  const key = typeof name === "string" ? "name" : "hash";
62
- return this.model?.parameters.filter(p => p[key] === name).forEach(p => p.value = val);
62
+ return this.model?.parameters?.filter(p => p[key] === name).forEach(p => p.value = val);
63
63
  }
64
64
 
65
65
  GetInteger(name: string | number): number {
66
66
  const key = typeof name === "string" ? "name" : "hash";
67
- return this.model?.parameters.find(p => p[key] === name)?.value as number ?? 0;
67
+ return this.model?.parameters?.find(p => p[key] === name)?.value as number ?? 0;
68
68
  }
69
69
 
70
70
  SetTrigger(name: string | number) {
71
71
  if (debug)
72
72
  console.log("SET TRIGGER", name);
73
73
  const key = typeof name === "string" ? "name" : "hash";
74
- return this.model?.parameters.filter(p => p[key] === name).forEach(p => p.value = true);
74
+ return this.model?.parameters?.filter(p => p[key] === name).forEach(p => p.value = true);
75
75
  }
76
76
 
77
77
  ResetTrigger(name: string | number) {
78
78
  const key = typeof name === "string" ? "name" : "hash";
79
- return this.model?.parameters.filter(p => p[key] === name).forEach(p => p.value = false);
79
+ return this.model?.parameters?.filter(p => p[key] === name).forEach(p => p.value = false);
80
80
  }
81
81
 
82
82
  IsInTransition(): boolean {
@@ -89,9 +89,11 @@ export class AnimatorController {
89
89
 
90
90
  FindState(name: string | undefined | null): State | null {
91
91
  if (!name) return null;
92
- for (const layer of this.model.layers) {
93
- for (const state of layer.stateMachine.states) {
94
- if (state.name === name) return state;
92
+ if (Array.isArray(this.model.layers)) {
93
+ for (const layer of this.model.layers) {
94
+ for (const state of layer.stateMachine.states) {
95
+ if (state.name === name) return state;
96
+ }
95
97
  }
96
98
  }
97
99
  return null;
@@ -117,6 +119,10 @@ export class AnimatorController {
117
119
  }
118
120
 
119
121
  clone() {
122
+ if (typeof this.model === "string") {
123
+ console.warn("AnimatorController has not been resolved, can not create model from string", this.model);
124
+ return null;
125
+ }
120
126
  // clone runtime controller but dont clone clip or action
121
127
  const clonedModel = deepClone(this.model, (_owner, _key, _value) => {
122
128
  if (_value === null || _value === undefined) return true;
@@ -435,6 +441,7 @@ export class AnimatorController {
435
441
  }
436
442
 
437
443
  private createActions(_animator: Animator) {
444
+ // console.trace(this.model, _animator);
438
445
  for (const layer of this.model.layers) {
439
446
  const sm = layer.stateMachine;
440
447
  for (let index = 0; index < sm.states.length; index++) {
@@ -444,9 +451,9 @@ export class AnimatorController {
444
451
  if (!state.transitions) {
445
452
  state.transitions = [];
446
453
  }
447
- for(const t of state.transitions) {
454
+ for (const t of state.transitions) {
448
455
  // can happen if conditions are empty in blender - the exporter seems to skip empty arrays
449
- if(!t.conditions) t.conditions = [];
456
+ if (!t.conditions) t.conditions = [];
450
457
  }
451
458
 
452
459
  // ensure we have a motion even if none was exported
@@ -8,7 +8,7 @@ import { Object3D } from "three";
8
8
  import { syncDestroy, syncInstantiate } from "../engine/engine_networking_instantiate";
9
9
  import { ConstructorConcrete, SourceIdentifier, IComponent, IGameObject, Constructor, GuidsMap, UIDProvider, Collision, ICollider } from "../engine/engine_types";
10
10
  import { addNewComponent, destroyComponentInstance, findObjectOfType, findObjectsOfType, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, moveComponentInstance, removeComponent } from "../engine/engine_components";
11
- import { findByGuid, destroy, InstantiateOptions, instantiate, HideFlags, foreachComponent, markAsInstancedRendered, isActiveInHierarchy, isActiveSelf, isUsingInstancing, setActive } from "../engine/engine_gameobject";
11
+ import { findByGuid, destroy, InstantiateOptions, instantiate, HideFlags, foreachComponent, markAsInstancedRendered, isActiveInHierarchy, isActiveSelf, isUsingInstancing, setActive, isDestroyed } from "../engine/engine_gameobject";
12
12
 
13
13
 
14
14
  // export interface ISerializationCallbackReceiver {
@@ -23,6 +23,10 @@ abstract class GameObject extends THREE.Object3D implements THREE.Object3D, IGam
23
23
 
24
24
  guid: string | undefined;
25
25
 
26
+ public static isDestroyed(go: THREE.Object3D): boolean {
27
+ return isDestroyed(go);
28
+ }
29
+
26
30
  public static setActive(go: THREE.Object3D, active: boolean, processStart: boolean = true, setVisible: boolean = true) {
27
31
  if (!go) return;
28
32
  setActive(go, active, setVisible);
@@ -8,6 +8,8 @@ import { getParam, isMobileDevice } from "../engine/engine_utils";
8
8
 
9
9
  import { Box3, Object3D, PerspectiveCamera, Vector2, Vector3 } from "three";
10
10
  import { OrbitControls as ThreeOrbitControls } from "three/examples/jsm/controls/OrbitControls";
11
+ import { EventSystem, EventSystemEvents } from "./ui/EventSystem";
12
+ import { transformWithEsbuild } from "vite";
11
13
 
12
14
  const freeCam = getParam("freecam");
13
15
 
@@ -60,11 +62,40 @@ export class OrbitControls extends Behaviour {
60
62
  private _enableTime: number = 0; // use to disable double click when double clicking on UI
61
63
  private _startedListeningToKeyEvents: boolean = false;
62
64
 
65
+ private _eventSystem?: EventSystem;
66
+ private _afterHandleInputFn?: any;
67
+
68
+ targetElement: HTMLElement | null = null;
69
+
63
70
  awake(): void {
64
71
  this._lookTargetPosition = new Vector3();
65
72
  this._startedListeningToKeyEvents = false;
66
73
  }
67
74
 
75
+ start() {
76
+ if (this._controls) {
77
+ const camGo = GameObject.getComponent(this.gameObject, Camera);
78
+ if (camGo && !this.setFromTargetPosition()) {
79
+ if (this.debugLog)
80
+ console.log("NO TARGET");
81
+ const forward = new Vector3(0, 0, -1).applyMatrix4(camGo.cam.matrixWorld);
82
+ this.setTarget(forward, true);
83
+ }
84
+ }
85
+ this.startCoroutine(this.startRaycastDelayed());
86
+
87
+ this._eventSystem = EventSystem.get(this.context) ?? undefined;
88
+ if (this._eventSystem) {
89
+ this._afterHandleInputFn = this.afterHandleInput.bind(this);
90
+ this._eventSystem.addEventListener(EventSystemEvents.AfterHandleInput, this._afterHandleInputFn!);
91
+ }
92
+ }
93
+
94
+ onDestroy() {
95
+ this._controls?.dispose();
96
+ this._eventSystem?.removeEventListener(EventSystemEvents.AfterHandleInput, this._afterHandleInputFn!);
97
+ }
98
+
68
99
  onEnable() {
69
100
  this._enableTime = this.context.time.time;
70
101
  const camGo = GameObject.getComponent(this.gameObject, Camera);
@@ -73,7 +104,10 @@ export class OrbitControls extends Behaviour {
73
104
  console.assert(cam !== null && cam !== undefined, "Missing camera", this);
74
105
  if (cam)
75
106
  this._cameraObject = cam;
76
- this._controls = new ThreeOrbitControls(cam!, this.context.renderer.domElement);
107
+ // Using the parent if possible to make it possible to disable input on the canvas
108
+ // for having HTML content behind it and still receive input
109
+ const element = this.targetElement ?? this.context.domElement;
110
+ this._controls = new ThreeOrbitControls(cam!, element);
77
111
  if (defaultKeys === undefined) defaultKeys = { ...this._controls.keys };
78
112
  }
79
113
 
@@ -115,6 +149,7 @@ export class OrbitControls extends Behaviour {
115
149
  this._controls.listenToKeyEvents(window.document.body);
116
150
  }
117
151
  }
152
+
118
153
  }
119
154
 
120
155
  onDisable() {
@@ -125,21 +160,11 @@ export class OrbitControls extends Behaviour {
125
160
  }
126
161
  }
127
162
 
128
- onDestroy() {
129
- this._controls?.dispose();
130
- }
131
-
132
- start() {
133
- if (this._controls) {
134
- const camGo = GameObject.getComponent(this.gameObject, Camera);
135
- if (camGo && !this.setFromTargetPosition()) {
136
- if (this.debugLog)
137
- console.log("NO TARGET");
138
- const forward = new Vector3(0, 0, -1).applyMatrix4(camGo.cam.matrixWorld);
139
- this.setTarget(forward, true);
140
- }
163
+ private _shouldDisable : boolean = false;
164
+ private afterHandleInput() {
165
+ if (this._controls && this._eventSystem) {
166
+ this._shouldDisable = this._eventSystem.hasActiveUI;
141
167
  }
142
- this.startCoroutine(this.startRaycastDelayed());
143
168
  }
144
169
 
145
170
  // we need to wait one frame (when starting the scene for the very first time)
@@ -221,7 +246,7 @@ export class OrbitControls extends Behaviour {
221
246
  if (this._controls && !this.context.isInXR) {
222
247
  if (this.debugLog)
223
248
  this._controls.domElement = this.context.renderer.domElement;
224
- this._controls.enabled = true;
249
+ this._controls.enabled = !this._shouldDisable;
225
250
  this._controls.update();
226
251
  }
227
252
  }
@@ -309,11 +334,11 @@ export class OrbitControls extends Behaviour {
309
334
  for (const object of objects)
310
335
  box.expandByObject(object);
311
336
 
312
- box.getSize( size );
313
- box.getCenter( center );
337
+ box.getSize(size);
338
+ box.getCenter(center);
314
339
 
315
340
  const maxSize = Math.max(size.x, size.y, size.z);
316
- const fitHeightDistance = maxSize / (2 * Math.atan( Math.PI * camera.fov / 360 ));
341
+ const fitHeightDistance = maxSize / (2 * Math.atan(Math.PI * camera.fov / 360));
317
342
  const fitWidthDistance = fitHeightDistance / camera.aspect;
318
343
  const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
319
344