@needle-tools/engine 2.61.0-pre → 2.62.2-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 (101) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/needle-engine.js +25826 -25563
  3. package/dist/needle-engine.umd.cjs +244 -246
  4. package/lib/engine/api.d.ts +1 -0
  5. package/lib/engine/api.js +1 -0
  6. package/lib/engine/api.js.map +1 -1
  7. package/lib/engine/debug/debug_overlay.js +9 -9
  8. package/lib/engine/debug/debug_overlay.js.map +1 -1
  9. package/lib/engine/engine.js +0 -2
  10. package/lib/engine/engine.js.map +1 -1
  11. package/lib/engine/engine_addressables.js +16 -5
  12. package/lib/engine/engine_addressables.js.map +1 -1
  13. package/lib/engine/engine_camera.d.ts +4 -0
  14. package/lib/engine/engine_camera.js +13 -0
  15. package/lib/engine/engine_camera.js.map +1 -0
  16. package/lib/engine/engine_element.d.ts +1 -0
  17. package/lib/engine/engine_element.js +17 -1
  18. package/lib/engine/engine_element.js.map +1 -1
  19. package/lib/engine/engine_input.d.ts +2 -0
  20. package/lib/engine/engine_input.js +14 -0
  21. package/lib/engine/engine_input.js.map +1 -1
  22. package/lib/engine/engine_loaders.d.ts +1 -0
  23. package/lib/engine/engine_loaders.js +12 -0
  24. package/lib/engine/engine_loaders.js.map +1 -1
  25. package/lib/engine/engine_setup.js +1 -1
  26. package/lib/engine/engine_setup.js.map +1 -1
  27. package/lib/engine/engine_types.d.ts +4 -0
  28. package/lib/engine/engine_types.js.map +1 -1
  29. package/lib/engine/extensions/extension_utils.js +1 -1
  30. package/lib/engine/extensions/extension_utils.js.map +1 -1
  31. package/lib/engine/js-extensions/Camera.d.ts +1 -0
  32. package/lib/engine/js-extensions/Camera.js +37 -0
  33. package/lib/engine/js-extensions/Camera.js.map +1 -0
  34. package/lib/engine/js-extensions/Layers.js +1 -0
  35. package/lib/engine/js-extensions/Layers.js.map +1 -1
  36. package/lib/engine/js-extensions/index.d.ts +2 -0
  37. package/lib/engine/js-extensions/index.js +3 -0
  38. package/lib/engine/js-extensions/index.js.map +1 -0
  39. package/lib/engine-components/Animation.js +12 -3
  40. package/lib/engine-components/Animation.js.map +1 -1
  41. package/lib/engine-components/CameraUtils.d.ts +1 -3
  42. package/lib/engine-components/CameraUtils.js +34 -17
  43. package/lib/engine-components/CameraUtils.js.map +1 -1
  44. package/lib/engine-components/Light.d.ts +4 -1
  45. package/lib/engine-components/Light.js +17 -2
  46. package/lib/engine-components/Light.js.map +1 -1
  47. package/lib/engine-components/OrbitControls.d.ts +4 -1
  48. package/lib/engine-components/OrbitControls.js +13 -2
  49. package/lib/engine-components/OrbitControls.js.map +1 -1
  50. package/lib/engine-components/timeline/PlayableDirector.js +27 -17
  51. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  52. package/lib/engine-components/timeline/TimelineTracks.js +7 -1
  53. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  54. package/lib/engine-components/ui/BaseUIComponent.d.ts +3 -0
  55. package/lib/engine-components/ui/BaseUIComponent.js +20 -10
  56. package/lib/engine-components/ui/BaseUIComponent.js.map +1 -1
  57. package/lib/engine-components/ui/Button.js +7 -3
  58. package/lib/engine-components/ui/Button.js.map +1 -1
  59. package/lib/engine-components/ui/EventSystem.js +23 -42
  60. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  61. package/lib/engine-components/ui/Graphic.d.ts +1 -0
  62. package/lib/engine-components/ui/Graphic.js +7 -0
  63. package/lib/engine-components/ui/Graphic.js.map +1 -1
  64. package/lib/engine-components/ui/Interfaces.d.ts +2 -1
  65. package/lib/engine-components/ui/RaycastUtils.js +2 -0
  66. package/lib/engine-components/ui/RaycastUtils.js.map +1 -1
  67. package/lib/engine-components/ui/Text.js +11 -1
  68. package/lib/engine-components/ui/Text.js.map +1 -1
  69. package/lib/needle-engine.d.ts +1 -0
  70. package/lib/needle-engine.js +1 -2
  71. package/lib/needle-engine.js.map +1 -1
  72. package/lib/tsconfig.tsbuildinfo +1 -1
  73. package/package.json +1 -1
  74. package/src/engine/api.ts +2 -1
  75. package/src/engine/debug/debug_overlay.ts +9 -9
  76. package/src/engine/engine.ts +0 -3
  77. package/src/engine/engine_addressables.ts +13 -5
  78. package/src/engine/engine_camera.ts +18 -0
  79. package/src/engine/engine_element.ts +17 -1
  80. package/src/engine/engine_input.ts +17 -5
  81. package/src/engine/engine_loaders.ts +13 -0
  82. package/src/engine/engine_setup.ts +2 -1
  83. package/src/engine/engine_types.ts +9 -4
  84. package/src/engine/extensions/extension_utils.ts +1 -1
  85. package/src/engine/js-extensions/Camera.ts +35 -0
  86. package/src/engine/js-extensions/Layers.ts +2 -1
  87. package/src/engine/js-extensions/index.ts +2 -0
  88. package/src/engine-components/Animation.ts +11 -3
  89. package/src/engine-components/CameraUtils.ts +42 -20
  90. package/src/engine-components/Light.ts +16 -3
  91. package/src/engine-components/OrbitControls.ts +18 -6
  92. package/src/engine-components/timeline/PlayableDirector.ts +25 -17
  93. package/src/engine-components/timeline/TimelineTracks.ts +9 -3
  94. package/src/engine-components/ui/BaseUIComponent.ts +21 -11
  95. package/src/engine-components/ui/Button.ts +7 -3
  96. package/src/engine-components/ui/EventSystem.ts +22 -42
  97. package/src/engine-components/ui/Graphic.ts +7 -0
  98. package/src/engine-components/ui/Interfaces.ts +3 -1
  99. package/src/engine-components/ui/RaycastUtils.ts +2 -1
  100. package/src/engine-components/ui/Text.ts +13 -2
  101. package/src/needle-engine.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.61.0-pre",
3
+ "version": "2.62.2-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",
package/src/engine/api.ts CHANGED
@@ -8,4 +8,5 @@ export * from "./debug/debug";
8
8
  export { validate } from "./engine_util_decorator"
9
9
  export { Gizmos } from "./engine_gizmos"
10
10
  export * from "./engine_scenetools";
11
- export * from "./engine_math"
11
+ export * from "./engine_math"
12
+ export * from "./js-extensions"
@@ -115,39 +115,37 @@ const logsContainerStyles = `
115
115
 
116
116
  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
117
117
 
118
-
119
- div {
118
+ div[data-needle_engine_debug_overlay] {
120
119
  font-family: 'Roboto', sans-serif;
121
120
  font-weight: 400;
122
121
  }
123
122
 
124
- strong {
123
+ div[data-needle_engine_debug_overlay] strong {
125
124
  font-weight: 700;
126
125
  }
127
126
 
128
- a {
127
+ div[data-needle_engine_debug_overlay] a {
129
128
  color: white;
130
129
  text-decoration: none;
131
130
  border-bottom: 1px solid rgba(255, 255, 255, 0.3);
132
131
  }
133
132
 
134
- a:hover {
133
+ div[data-needle_engine_debug_overlay] a:hover {
135
134
  text-decoration: none;
136
135
  border: none;
137
136
  }
138
137
 
139
- .log strong {
138
+ div[data-needle_engine_debug_overlay] .log strong {
140
139
  color: rgba(200,200,200,.9);
141
140
  }
142
141
 
143
- .warn strong {
142
+ div[data-needle_engine_debug_overlay] .warn strong {
144
143
  color: rgba(255,255,230, 1);
145
144
  }
146
145
 
147
- .error strong {
146
+ div[data-needle_engine_debug_overlay] .error strong {
148
147
  color: rgba(255,100,120, 1);
149
148
  }
150
-
151
149
  `;
152
150
 
153
151
  function getLogsContainer(domElement: HTMLElement): HTMLElement {
@@ -155,6 +153,7 @@ function getLogsContainer(domElement: HTMLElement): HTMLElement {
155
153
  return errorContainer.get(domElement)!;
156
154
  } else {
157
155
  const container = document.createElement("div");
156
+ container.setAttribute("data-needle_engine_debug_overlay", "");
158
157
  container.classList.add(arContainerClassName);
159
158
  container.classList.add("desktop");
160
159
  container.classList.add("debug-container");
@@ -206,6 +205,7 @@ function getMessageContainer(type: LogType, msg: string): HTMLElement {
206
205
  }
207
206
  }
208
207
  const element = document.createElement("div");
208
+ element.setAttribute("data-id", "__needle_engine_debug_overlay");
209
209
  element.style.marginRight = "5px";
210
210
  element.style.padding = ".5em";
211
211
  element.style.backgroundColor = "rgba(0,0,0,.9)";
@@ -1,8 +1,5 @@
1
1
  import "./engine_hot_reload"
2
2
 
3
- import * as layers from "./js-extensions/Layers";
4
- layers.patchLayers();
5
-
6
3
  import * as engine_setup from "./engine_setup";
7
4
  import * as engine_scenetools from "./engine_scenetools";
8
5
  import "./tests/test_utils";
@@ -60,7 +60,7 @@ export class AssetReference {
60
60
  return ref;
61
61
  }
62
62
 
63
- private static currentlyInstantiating: string[] = [];
63
+ private static currentlyInstantiating: Map<string, number> = new Map<string, number>();
64
64
 
65
65
  get asset(): any {
66
66
  return this._glbRoot ?? this._asset;
@@ -236,12 +236,16 @@ export class AssetReference {
236
236
  }
237
237
  }
238
238
 
239
- if (AssetReference.currentlyInstantiating.indexOf(this.uri) >= 0) {
240
- console.error("Recursive instantiation of", this.uri);
239
+ let count = AssetReference.currentlyInstantiating.get(this.uri);
240
+ // allow up to 100 instantiations of the same prefab in the same frame
241
+ if (count !== undefined && count >= 100) {
242
+ console.error("Recursive or too many instantiations of " + this.uri + " in the same frame (" + count + ")");
241
243
  return null;
242
244
  }
243
245
  try {
244
- AssetReference.currentlyInstantiating.push(this.uri);
246
+ if (count === undefined) count = 0;
247
+ count += 1;
248
+ AssetReference.currentlyInstantiating.set(this.uri, count);
245
249
  if (networked) {
246
250
  options.context = context;
247
251
  const prefab = this.asset;
@@ -259,7 +263,11 @@ export class AssetReference {
259
263
  }
260
264
  }
261
265
  finally {
262
- context.post_render_callbacks.push(() => AssetReference.currentlyInstantiating.pop());
266
+ context.post_render_callbacks.push(() => {
267
+ if (count === undefined || count < 0) count = 0;
268
+ else count -= 1;
269
+ AssetReference.currentlyInstantiating.set(this.uri, count)
270
+ });
263
271
  }
264
272
 
265
273
  }
@@ -0,0 +1,18 @@
1
+ import { ICameraController } from "./engine_types";
2
+ import { Camera } from "three";
3
+
4
+
5
+ const $cameraController = Symbol("cameraController");
6
+
7
+ export function getCameraController(cam: Camera): ICameraController | null {
8
+ return cam[$cameraController];
9
+ }
10
+
11
+ export function setCameraController(cam: Camera, cameraController: ICameraController, active: boolean) {
12
+ if (active)
13
+ cam[$cameraController] = cameraController;
14
+ else{
15
+ if(cam[$cameraController] === cameraController)
16
+ cam[$cameraController] = null;
17
+ }
18
+ }
@@ -79,6 +79,12 @@ export class EngineElement extends HTMLElement implements INeedleEngineComponent
79
79
  public get loadingProgress01(): number { return this._loadingProgress01; }
80
80
  public get loadingFinished(): boolean { return this.loadingProgress01 > .999; }
81
81
 
82
+ public get cameraControls(): boolean {
83
+ const attr = this.getAttribute("camera-controls");
84
+ if(attr === null || attr === "False" || attr === "false" || attr === "0") return false;
85
+ return true;
86
+ }
87
+
82
88
  public getContext(): Promise<Context> {
83
89
  return new Promise((res, _rej) => {
84
90
  if (this._context && this.loadingFinished) {
@@ -237,7 +243,17 @@ export class EngineElement extends HTMLElement implements INeedleEngineComponent
237
243
  }
238
244
 
239
245
  static get observedAttributes() {
240
- return ["hash", "src", "loadstart", "progress", "loadfinished", "dracoDecoderPath", "dracoDecoderType", "ktx2DecoderPath"];
246
+ return [
247
+ "hash",
248
+ "src",
249
+ "camera-controls",
250
+ "loadstart",
251
+ "progress",
252
+ "loadfinished",
253
+ "dracoDecoderPath",
254
+ "dracoDecoderType",
255
+ "ktx2DecoderPath"
256
+ ];
241
257
  }
242
258
 
243
259
  attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
@@ -220,6 +220,7 @@ export class Input extends EventTarget {
220
220
  private _pointerPositionDown: THREE.Vector2[] = [new THREE.Vector2()];
221
221
  private _pointerDownTime: number[] = [];
222
222
  private _pointerUpTime: number[] = [];
223
+ private _pointerUpTimestamp: number[] = [];
223
224
  private _pointerIds: number[] = [];
224
225
  private _pointerTypes: string[] = [""];
225
226
  private _mouseWheelChanged: boolean[] = [false];
@@ -411,13 +412,15 @@ export class Input extends EventTarget {
411
412
  }
412
413
  }
413
414
 
414
- private onTouchUp(evt) {
415
+ private onTouchUp(evt: TouchEvent) {
415
416
  if (evt.changedTouches.length <= 0) return;
416
417
  for (let i = 0; i < evt.changedTouches.length; i++) {
417
418
  const touch = evt.changedTouches[i];
418
- const id = this.getPointerIndex(touch.identifier)
419
- if (debug)
420
- showBalloonMessage(`touch up #${id}, identifier:${touch.identifier}`);
419
+ const id = this.getPointerIndex(touch.identifier);
420
+
421
+ if (!this.isNewEvent(evt.timeStamp, id, this._pointerUpTimestamp)) continue;
422
+
423
+ if (debug) showBalloonMessage(`touch up #${id}, identifier:${touch.identifier}`);
421
424
  const args: PointerEventArgs = { button: id, clientX: touch.clientX, clientY: touch.clientY, pointerType: PointerType.Touch, source: evt };
422
425
  this.onUp(args);
423
426
  }
@@ -440,11 +443,20 @@ export class Input extends EventTarget {
440
443
  private onMouseUp(evt: MouseEvent) {
441
444
  if (evt.defaultPrevented) return;
442
445
  let i = evt.button;
446
+ if (!this.isNewEvent(evt.timeStamp, i, this._pointerUpTimestamp)) return;
443
447
  this.onUp({ button: i, clientX: evt.clientX, clientY: evt.clientY, pointerType: PointerType.Mouse, source: evt });
444
448
  }
445
449
 
450
+ // Prevent the same event being handled twice (e.g. on touch we get a mouseUp and touchUp evt with the same timestamp)
451
+ private isNewEvent(timestamp: number, index: number, arr: number[]): boolean {
452
+ while (arr.length <= index) arr.push(-1);
453
+ if (timestamp === arr[index]) return false;
454
+ arr[index] = timestamp;
455
+ return true;
456
+ }
457
+
446
458
  private isInRect(e: { clientX: number, clientY: number }): boolean {
447
- if(this.context.isInXR) return true;
459
+ if (this.context.isInXR) return true;
448
460
  const rect = this.context.domElement.getBoundingClientRect();
449
461
  const px = e.clientX;
450
462
  const py = e.clientY;
@@ -3,6 +3,8 @@ import { Context } from "./engine_setup"
3
3
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
4
4
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
5
5
  import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
6
+ import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
7
+
6
8
  import { getParam } from "./engine_utils";
7
9
 
8
10
  const debug = getParam("debugdecoders");
@@ -11,6 +13,7 @@ const DEFAULT_DRACO_DECODER_LOCATION ='https://www.gstatic.com/draco/versioned/d
11
13
  const DEFAULT_KTX2_TRANSCODER_LOCATION ='https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/';
12
14
 
13
15
  let dracoLoader: DRACOLoader;
16
+ let meshoptDecoder: MeshoptDecoder;
14
17
  let ktx2Loader: KTX2Loader;
15
18
 
16
19
  export function setDracoDecoderPath(path: string | undefined) {
@@ -40,6 +43,11 @@ export function setKtx2TranscoderPath(path: string) {
40
43
  }
41
44
  }
42
45
 
46
+ export function setMeshoptDecoder(_meshoptDecoder: any) {
47
+ if (_meshoptDecoder !== undefined)
48
+ meshoptDecoder = _meshoptDecoder;
49
+ }
50
+
43
51
  export function addDracoAndKTX2Loaders(loader: GLTFLoader, context: Context) {
44
52
  if (!dracoLoader) {
45
53
  dracoLoader = new DRACOLoader();
@@ -54,7 +62,12 @@ export function addDracoAndKTX2Loaders(loader: GLTFLoader, context: Context) {
54
62
  if (context.renderer)
55
63
  ktx2Loader.detectSupport(context.renderer);
56
64
  }
65
+ if (!meshoptDecoder) {
66
+ meshoptDecoder = MeshoptDecoder;
67
+ if (debug) console.log("Using the default meshopt decoder");
68
+ }
57
69
 
58
70
  loader.setDRACOLoader(dracoLoader);
59
71
  loader.setKTX2Loader(ktx2Loader);
72
+ loader.setMeshoptDecoder(meshoptDecoder);
60
73
  }
@@ -504,7 +504,6 @@ export class Context implements IContext {
504
504
  this.domElement.prepend(this.renderer.domElement);
505
505
 
506
506
  Context._current = this;
507
- ContextRegistry.dispatchCallback(ContextEvent.ContextCreated, this);
508
507
 
509
508
  // Setup
510
509
  Context._current = this;
@@ -591,6 +590,8 @@ export class Context implements IContext {
591
590
 
592
591
  if (debug)
593
592
  logHierarchy(this.scene, true);
593
+
594
+ ContextRegistry.dispatchCallback(ContextEvent.ContextCreated, this);
594
595
  }
595
596
 
596
597
  private render(_, frame) {
@@ -30,10 +30,11 @@ export declare type CoroutineData = {
30
30
  export interface IContext {
31
31
  alias?: string | null;
32
32
 
33
- scene : Scene;
34
- renderer : WebGLRenderer;
35
- mainCamera : Camera | null;
36
- domElement : HTMLElement;
33
+ scene: Scene;
34
+ renderer: WebGLRenderer;
35
+ mainCamera: Camera | null;
36
+ mainCameraComponent: ICamera | undefined;
37
+ domElement: HTMLElement;
37
38
 
38
39
  scripts: IComponent[];
39
40
  scripts_pausedChanged: IComponent[];
@@ -158,6 +159,10 @@ export declare interface ICamera extends IComponent {
158
159
  screenPointToRay(x: number, y: number, ray?: Ray): Ray;
159
160
  }
160
161
 
162
+ export declare interface ICameraController {
163
+ get isCameraController(): boolean;
164
+ }
165
+
161
166
  export declare interface ILight extends IComponent {
162
167
  intensity: number;
163
168
  color: Color;
@@ -43,7 +43,7 @@ function internalResolve(paths: DependencyInfo[], parser: GLTFParser, obj, promi
43
43
  // handle json pointer in string variable
44
44
  if (typeof val === "string") {
45
45
  const ext = resolveExtension(parser, val);
46
- if (ext !== null) {
46
+ if (ext !== null && ext !== undefined) {
47
47
  if (typeof ext.then === "function")
48
48
  promises.push(ext.then(res => obj[key] = res));
49
49
  else obj[key] = ext;
@@ -0,0 +1,35 @@
1
+ import { PerspectiveCamera } from "three";
2
+
3
+ // Wrap camera FOV to allow animation of fov
4
+ Object.defineProperty(PerspectiveCamera.prototype, "fov", {
5
+ get: function () {
6
+ return this._fov;;
7
+ },
8
+ set: function (val) {
9
+ const changed = val !== this._fov;
10
+ this._fov = val;
11
+ if (changed && this.view !== undefined) this.updateProjectionMatrix();
12
+ }
13
+ });
14
+
15
+ Object.defineProperty(PerspectiveCamera.prototype, "near", {
16
+ get: function () {
17
+ return this._near;
18
+ },
19
+ set: function (val) {
20
+ const changed = val !== this._near;
21
+ this._near = val;
22
+ if (changed && this.view !== undefined) this.updateProjectionMatrix();
23
+ }
24
+ });
25
+
26
+ Object.defineProperty(PerspectiveCamera.prototype, "far", {
27
+ get: function () {
28
+ return this._far;
29
+ },
30
+ set: function (val) {
31
+ const changed = val !== this._far;
32
+ this._far = val;
33
+ if (changed && this.view !== undefined) this.updateProjectionMatrix();
34
+ }
35
+ });
@@ -16,4 +16,5 @@ export function patchLayers() {
16
16
  if(this[$customVisibilityFlag] === false) return false;
17
17
  return origTest.call(this, layer);
18
18
  };
19
- }
19
+ }
20
+ patchLayers();
@@ -0,0 +1,2 @@
1
+ import "./Layers"
2
+ import "./Camera"
@@ -145,12 +145,19 @@ export class Animation extends Behaviour {
145
145
  }
146
146
 
147
147
  play(clipOrNumber: AnimationClip | number | string | undefined, options?: PlayOptions): Promise<AnimationAction> | void {
148
+ if (debug) console.log("PLAY", clipOrNumber)
148
149
  this.init();
149
- if (!this.mixer) return;
150
+ if (!this.mixer) {
151
+ if (debug) console.warn("Missing mixer", this);
152
+ return;
153
+ }
150
154
  if (clipOrNumber === undefined) clipOrNumber = 0;
151
155
  let clip: AnimationClip | undefined = clipOrNumber as AnimationClip;
152
156
  if (typeof clipOrNumber === 'number') {
153
- if (clipOrNumber >= this.animations.length) return;
157
+ if (clipOrNumber >= this.animations.length) {
158
+ if (debug) console.log("No animation at index", clipOrNumber)
159
+ return;
160
+ }
154
161
  clip = this.animations[clipOrNumber];
155
162
  }
156
163
  else if (typeof clipOrNumber === "string") {
@@ -205,7 +212,8 @@ export class Animation extends Behaviour {
205
212
  action.loop = options.loop ? THREE.LoopRepeat : THREE.LoopOnce;
206
213
  else action.loop = THREE.LoopOnce;
207
214
  action.play();
208
- // console.log("PLAY", action.getClip().name, action)
215
+ if (debug)
216
+ console.log("PLAY", action.getClip().name, action)
209
217
 
210
218
  const handle = new AnimationHandle(action, this.mixer!, options, _ => {
211
219
  this._handles.splice(this._handles.indexOf(handle), 1);
@@ -1,32 +1,54 @@
1
1
  import { OrbitControls } from "./OrbitControls";
2
- import { Camera } from "./Camera";
3
2
  import { addNewComponent } from "../engine/engine_components";
4
- import { Color, Object3D, Scene, Vector3 } from "three";
5
- import { ICamera, SourceIdentifier } from "../engine/engine_types";
6
- import { lookAtInverse } from "../engine/engine_three_utils";
3
+ import { Object3D } from "three";
4
+ import { ICamera } from "../engine/engine_types";
7
5
  import { RGBAColor } from "./js-extensions/RGBAColor";
8
6
  import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry";
9
-
7
+ import { getCameraController } from "../engine/engine_camera";
8
+ import { Camera } from "./Camera";
9
+ import { EngineElement } from "../engine/engine_element";
10
10
 
11
11
  ContextRegistry.registerCallback(ContextEvent.MissingCamera, (evt) => {
12
- createCameraWithOrbitControl(evt.context.scene, "unknown");
13
- });
12
+ const scene = evt.context.scene;
13
+ const srcId = "unknown";
14
14
 
15
+ const cameraObject = new Object3D();
16
+ scene.add(cameraObject);
15
17
 
16
- export function createCameraWithOrbitControl(scene: Scene, source: SourceIdentifier): ICamera {
17
- const srcId = source;
18
- const go = new Object3D();
19
- scene.add(go);
20
18
  const camInstance = new Camera();
21
- const cam = addNewComponent(go, camInstance, true) as ICamera
19
+ const cam = addNewComponent(cameraObject, camInstance, true) as ICamera
22
20
  cam.sourceId = srcId;
23
21
  cam.clearFlags = 2;
24
22
  cam.backgroundColor = new RGBAColor(0.5, 0.5, 0.5, 1);
25
- const orbit = addNewComponent(go, new OrbitControls(), false) as OrbitControls;
26
- orbit.sourceId = srcId;
27
- go.position.x = -2;
28
- go.position.y = 2;
29
- go.position.z = 2;
30
- lookAtInverse(go, new Vector3(0, 0, 0));
31
- return cam as Camera;
32
- }
23
+
24
+ cameraObject.position.x = -2;
25
+ cameraObject.position.y = 2;
26
+ cameraObject.position.z = 2;
27
+ return cam;
28
+ });
29
+
30
+ ContextRegistry.registerCallback(ContextEvent.ContextCreated, (evt) => {
31
+ if (!evt.context.mainCamera) return;
32
+
33
+ // check if the <needle-engine controls> is not set to false
34
+ const engineElement = evt.context.domElement as EngineElement
35
+
36
+ if (engineElement?.cameraControls === true) {
37
+
38
+ // Check if something else already acts as a camera controller
39
+ const existing = getCameraController(evt.context.mainCamera);
40
+ if (existing?.isCameraController === true) {
41
+ return;
42
+ }
43
+
44
+ const cam = evt.context.mainCameraComponent;
45
+ const cameraObject = cam?.gameObject;
46
+ if (cameraObject) {
47
+ const orbit = addNewComponent(cameraObject, new OrbitControls(), false) as OrbitControls;
48
+ orbit.sourceId = "unknown";
49
+ }
50
+ else {
51
+ console.warn("Missing camera object, can not add orbit controls")
52
+ }
53
+ }
54
+ })
@@ -95,7 +95,17 @@ export class Light extends Behaviour implements ILight {
95
95
  public innerSpotAngle: number = 1;
96
96
 
97
97
  @serializable(Color)
98
- public color: THREE.Color = new THREE.Color(0xffffff);
98
+ set color(val: Color) {
99
+ this._color = val;
100
+ if (this.light !== undefined) {
101
+ this.light.color = val;
102
+ }
103
+ }
104
+ get color(): Color {
105
+ if (this.light) return this.light.color;
106
+ return this._color;
107
+ }
108
+ public _color: THREE.Color = new THREE.Color(0xffffff);
99
109
 
100
110
  @serializable()
101
111
  set shadowNearPlane(val: number) {
@@ -242,13 +252,14 @@ export class Light extends Behaviour implements ILight {
242
252
 
243
253
  awake() {
244
254
  this.color = new THREE.Color(this.color ?? 0xffffff);
245
- if(debug) console.log(this.name, this);
255
+ if (debug) console.log(this.name, this);
246
256
  }
247
257
 
248
258
  onEnable(): void {
249
259
  this.createLight();
250
260
  if (this.isBaked) return;
251
261
  else if (this.light) {
262
+ this.light.visible = true;
252
263
  if (this.selfIsLight) {
253
264
  // nothing to do
254
265
  }
@@ -257,12 +268,14 @@ export class Light extends Behaviour implements ILight {
257
268
  }
258
269
  if (this.type === LightType.Directional)
259
270
  this.startCoroutine(this.updateMainLightRoutine(), FrameEvent.LateUpdate);
260
-
261
271
  this._webXRStartedListener = WebXR.addEventListener(WebXREvent.XRStarted, this.onWebXRStarted.bind(this));
262
272
  this._webXREndedListener = WebXR.addEventListener(WebXREvent.XRStopped, this.onWebXREnded.bind(this));
263
273
  }
264
274
 
265
275
  onDisable() {
276
+ if (this.light) {
277
+ this.light.visible = false;
278
+ }
266
279
  WebXR.removeEventListener(WebXREvent.XRStarted, this._webXRStartedListener);
267
280
  WebXR.removeEventListener(WebXREvent.XRStopped, this._webXREndedListener);
268
281
  }
@@ -6,17 +6,23 @@ import { RaycastOptions } from "../engine/engine_physics";
6
6
  import { serializable } from "../engine/engine_serialization_decorator";
7
7
  import { getParam, isMobileDevice } from "../engine/engine_utils";
8
8
 
9
- import { Box3, Object3D, PerspectiveCamera, Vector2, Vector3 } from "three";
9
+ import { Camera as ThreeCamera, Box3, Object3D, PerspectiveCamera, Vector2, Vector3 } from "three";
10
10
  import { OrbitControls as ThreeOrbitControls } from "three/examples/jsm/controls/OrbitControls";
11
11
  import { EventSystem, EventSystemEvents } from "./ui/EventSystem";
12
- import { transformWithEsbuild } from "vite";
12
+ import { ICameraController } from "../engine/engine_types";
13
+ import { setCameraController } from "../engine/engine_camera";
13
14
 
14
15
  const freeCam = getParam("freecam");
15
16
 
16
17
  const disabledKeys = { LEFT: "", UP: "", RIGHT: "", BOTTOM: "" };
17
18
  let defaultKeys: any = undefined;
18
19
 
19
- export class OrbitControls extends Behaviour {
20
+ export class OrbitControls extends Behaviour implements ICameraController {
21
+
22
+ get isCameraController(): boolean {
23
+ return true;
24
+ }
25
+
20
26
  public get controls() {
21
27
  return this._controls;
22
28
  }
@@ -64,6 +70,7 @@ export class OrbitControls extends Behaviour {
64
70
 
65
71
  private _eventSystem?: EventSystem;
66
72
  private _afterHandleInputFn?: any;
73
+ private _camera : Camera | null = null;
67
74
 
68
75
  targetElement: HTMLElement | null = null;
69
76
 
@@ -98,8 +105,10 @@ export class OrbitControls extends Behaviour {
98
105
 
99
106
  onEnable() {
100
107
  this._enableTime = this.context.time.time;
101
- const camGo = GameObject.getComponent(this.gameObject, Camera);
102
- const cam = camGo?.cam;
108
+ const cameraComponent = GameObject.getComponent(this.gameObject, Camera);
109
+ this._camera = cameraComponent;
110
+ const cam = cameraComponent?.cam;
111
+ if (cam) setCameraController(cam, this, true);
103
112
  if (!this._controls) {
104
113
  console.assert(cam !== null && cam !== undefined, "Missing camera", this);
105
114
  if (cam)
@@ -153,6 +162,9 @@ export class OrbitControls extends Behaviour {
153
162
  }
154
163
 
155
164
  onDisable() {
165
+ if (this._camera?.cam) {
166
+ setCameraController(this._camera.cam, this, false);
167
+ }
156
168
  if (this._controls) {
157
169
  this._controls.enabled = false;
158
170
  this._controls.autoRotate = false;
@@ -160,7 +172,7 @@ export class OrbitControls extends Behaviour {
160
172
  }
161
173
  }
162
174
 
163
- private _shouldDisable : boolean = false;
175
+ private _shouldDisable: boolean = false;
164
176
  private afterHandleInput() {
165
177
  if (this._controls && this._eventSystem) {
166
178
  this._shouldDisable = this._eventSystem.hasActiveUI;