@needle-tools/engine 4.4.0-alpha.3 → 4.4.0-alpha.5

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 (89) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/gltf-progressive.js +54 -54
  3. package/dist/gltf-progressive.light.js +54 -54
  4. package/dist/gltf-progressive.light.min.js +7 -7
  5. package/dist/gltf-progressive.min.js +7 -7
  6. package/dist/needle-engine.bundle.js +9120 -8879
  7. package/dist/needle-engine.bundle.light.js +9124 -8883
  8. package/dist/needle-engine.bundle.light.min.js +155 -142
  9. package/dist/needle-engine.bundle.light.umd.cjs +125 -112
  10. package/dist/needle-engine.bundle.min.js +155 -142
  11. package/dist/needle-engine.bundle.umd.cjs +127 -114
  12. package/dist/needle-engine.d.ts +9 -9
  13. package/dist/needle-engine.js +544 -543
  14. package/dist/needle-engine.light.js +544 -543
  15. package/dist/needle-engine.light.min.js +1 -1
  16. package/dist/needle-engine.light.umd.cjs +1 -1
  17. package/dist/needle-engine.min.js +1 -1
  18. package/dist/needle-engine.umd.cjs +1 -1
  19. package/dist/postprocessing.js +38 -38
  20. package/dist/postprocessing.light.js +38 -38
  21. package/dist/postprocessing.light.min.js +2 -2
  22. package/dist/postprocessing.min.js +2 -2
  23. package/dist/three-examples.js +1205 -1205
  24. package/dist/three-examples.light.js +1205 -1205
  25. package/dist/three-examples.light.min.js +15 -15
  26. package/dist/three-examples.min.js +15 -15
  27. package/dist/three-mesh-ui.js +9 -9
  28. package/dist/three-mesh-ui.light.js +9 -9
  29. package/dist/three-mesh-ui.light.min.js +9 -9
  30. package/dist/three-mesh-ui.min.js +9 -9
  31. package/dist/three.js +239 -238
  32. package/dist/three.light.js +239 -238
  33. package/dist/three.light.min.js +27 -27
  34. package/dist/three.light.umd.cjs +15 -15
  35. package/dist/three.min.js +27 -27
  36. package/dist/three.umd.cjs +15 -15
  37. package/dist/vendor.js +24 -24
  38. package/dist/vendor.light.js +24 -24
  39. package/dist/vendor.light.min.js +2 -2
  40. package/dist/vendor.min.js +2 -2
  41. package/lib/engine/engine_input.d.ts +7 -0
  42. package/lib/engine/engine_input.js +12 -0
  43. package/lib/engine/engine_input.js.map +1 -1
  44. package/lib/engine/engine_serialization_decorator.js +4 -0
  45. package/lib/engine/engine_serialization_decorator.js.map +1 -1
  46. package/lib/engine/engine_three_utils.d.ts +26 -1
  47. package/lib/engine/engine_three_utils.js +43 -0
  48. package/lib/engine/engine_three_utils.js.map +1 -1
  49. package/lib/engine/engine_utils_format.js +11 -5
  50. package/lib/engine/engine_utils_format.js.map +1 -1
  51. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +1 -0
  52. package/lib/engine/webcomponents/needle menu/needle-menu.js +3 -3
  53. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  54. package/lib/engine-components/Camera.d.ts +2 -2
  55. package/lib/engine-components/Camera.js +4 -6
  56. package/lib/engine-components/Camera.js.map +1 -1
  57. package/lib/engine-components/CameraUtils.js +32 -13
  58. package/lib/engine-components/CameraUtils.js.map +1 -1
  59. package/lib/engine-components/Component.d.ts +4 -1
  60. package/lib/engine-components/Component.js +4 -1
  61. package/lib/engine-components/Component.js.map +1 -1
  62. package/lib/engine-components/ContactShadows.js +8 -2
  63. package/lib/engine-components/ContactShadows.js.map +1 -1
  64. package/lib/engine-components/OrbitControls.d.ts +3 -0
  65. package/lib/engine-components/OrbitControls.js +34 -10
  66. package/lib/engine-components/OrbitControls.js.map +1 -1
  67. package/lib/engine-components/SyncedTransform.d.ts +3 -2
  68. package/lib/engine-components/SyncedTransform.js +28 -18
  69. package/lib/engine-components/SyncedTransform.js.map +1 -1
  70. package/lib/engine-components/postprocessing/PostProcessingHandler.js +6 -2
  71. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  72. package/lib/engine-components/utils/EnvironmentScene.d.ts +5 -0
  73. package/lib/engine-components/utils/EnvironmentScene.js +206 -0
  74. package/lib/engine-components/utils/EnvironmentScene.js.map +1 -0
  75. package/package.json +1 -1
  76. package/src/engine/engine_input.ts +13 -0
  77. package/src/engine/engine_serialization_decorator.ts +4 -0
  78. package/src/engine/engine_three_utils.ts +52 -1
  79. package/src/engine/engine_utils_format.ts +13 -5
  80. package/src/engine/webcomponents/needle menu/needle-menu.ts +3 -3
  81. package/src/engine-components/Camera.ts +9 -6
  82. package/src/engine-components/CameraUtils.ts +35 -13
  83. package/src/engine-components/Component.ts +5 -2
  84. package/src/engine-components/ContactShadows.ts +8 -2
  85. package/src/engine-components/OrbitControls.ts +35 -10
  86. package/src/engine-components/SyncedTransform.ts +37 -25
  87. package/src/engine-components/postprocessing/PostProcessingHandler.ts +7 -2
  88. package/src/engine-components/utils/EnvironmentScene.ts +246 -0
  89. package/src/engine/webcomponents/needle menu/dist/needle-menu.js +0 -662
@@ -417,6 +417,19 @@ export class Input implements IInput {
417
417
  /** Was a double click detected for the primary pointer? */
418
418
  get doubleClick(): boolean { return this._pointerDoubleClick[0]; }
419
419
 
420
+ /**
421
+ * Get a connected Gamepad
422
+ * Note: For a gamepad to be available to the browser it must have received input before while the page was focused.
423
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API
424
+ * @returns The gamepad or null if no gamepad is connected
425
+ */
426
+ getGamepad(index: number = 0): Gamepad | null {
427
+ if (typeof navigator !== "undefined" && "getGamepads" in navigator) {
428
+ return navigator.getGamepads()[index] || null;
429
+ }
430
+ return null;
431
+ }
432
+
420
433
  private readonly _setCursorTypes: CursorTypeName[] = [];
421
434
 
422
435
  /** @deprecated use setCursor("pointer") */
@@ -26,6 +26,10 @@ export const serializable = function <T>(type?: Constructor<T> | null | Array<Co
26
26
  }
27
27
 
28
28
  return function (_target: any, _propertyKey: string | { name: string }) {
29
+ if (!_target) {
30
+ console.error("Found @serializable decorator without a target");
31
+ return;
32
+ }
29
33
  // The _propertyKey parameter is a string in TS4 with experimentalDecorators
30
34
  // but a ClassFieldDecoratorContext in TS5 without.
31
35
  // Seems when a different TS version is used in VSCode for editor checking, we get errors here
@@ -1,4 +1,4 @@
1
- import { AnimationAction, Box3, Box3Helper, Color, Euler, GridHelper, Layers, Material, Mesh, MeshStandardMaterial, Object3D, PerspectiveCamera, PlaneGeometry, Quaternion, Scene, ShadowMaterial, Texture, Uniform, Vector3 } from "three";
1
+ import { AnimationAction, Box3, Box3Helper, Camera, Color, Euler, GridHelper, Layers, Material, Mesh, MeshStandardMaterial, Object3D, PerspectiveCamera, PlaneGeometry, Quaternion, Scene, ShadowMaterial, Texture, Uniform, Vector2Like, Vector3 } from "three";
2
2
  import { ShaderMaterial, WebGLRenderer } from "three";
3
3
  import { GroundedSkybox } from "three/examples/jsm/objects/GroundedSkybox.js";
4
4
 
@@ -71,6 +71,57 @@ export function lookAtObject(object: Object3D, target: Object3D, keepUpDirection
71
71
  }
72
72
 
73
73
 
74
+ /**
75
+ * Look at a 2D point in screen space
76
+ * @param object the object to look at the point
77
+ * @param target the target point in 2D screen space XY e.g. from a mouse event
78
+ * @param camera the camera to use for the lookAt
79
+ * @param factor the factor to multiply the distance from the camera to the object. Default is 1
80
+ * @returns the target point in world space
81
+ *
82
+ * @example Needle Engine Component
83
+ * ```ts
84
+ * export class MyLookAtComponent extends Behaviour {
85
+ * update() {
86
+ * lookAtScreenPoint(this.gameObject, this.context.input.mousePosition, this.context.mainCamera);
87
+ * }
88
+ * }
89
+ * ```
90
+ *
91
+ * @example Look at from browser mouse move event
92
+ * ```ts
93
+ * window.addEventListener("mousemove", (e) => {
94
+ * lookAtScreenPoint(object, new Vector2(e.clientX, e.clientY), camera);
95
+ * });
96
+ * ```
97
+ */
98
+ export function lookAtScreenPoint(object: Object3D, target: Vector2Like, camera: Camera, factor : number = 1): Vector3 | null {
99
+
100
+ if (camera) {
101
+ const pos = getTempVector(0, 0, 0);
102
+ const ndcx = (target.x / window.innerWidth) * 2 - 1;
103
+ const ndcy = -(target.y / window.innerHeight) * 2 + 1;
104
+ pos.set(
105
+ ndcx,
106
+ ndcy,
107
+ 0
108
+ );
109
+ pos.unproject(camera);
110
+ // get distance from object to camera
111
+ const camPos = camera.worldPosition;
112
+ const dist = object.worldPosition.distanceTo(camPos);
113
+ // Create direction from camera through cursor point
114
+ const dir = pos.sub(camPos);
115
+ dir.multiplyScalar(factor * 3.6 * dist);
116
+ const targetPoint = camera.worldPosition.add(dir);
117
+ object.lookAt(targetPoint);
118
+ return targetPoint;
119
+ }
120
+ return null;
121
+ }
122
+
123
+
124
+
74
125
  const _tempVecs = new CircularBuffer(() => new Vector3(), 100);
75
126
 
76
127
  /** Gets a temporary vector. If a vector is passed in it will be copied to the temporary vector
@@ -59,13 +59,21 @@ export async function tryDetermineFileTypeFromURL(url: string, useExtension: boo
59
59
  }
60
60
  }
61
61
 
62
+
62
63
  // If the URL doesnt contain a filetype we need to check the header
63
64
  // This is the case for example if we load a file from a data url
64
- const newUrl = new URL(url);
65
- // Adding a URL parameter to avoid the brower to bust the full cache
66
- // If we don't do this the file that might already be disc cached will be deleted from the cache
67
- newUrl.searchParams.append("range", "true");
68
- const header = await fetch(newUrl, {
65
+
66
+ if(url.startsWith("blob:")) {
67
+ // We can't modify the blob URL
68
+ }
69
+ else {
70
+ const newUrl = new URL(url);
71
+ // Adding a URL parameter to avoid the brower to bust the full cache
72
+ // If we don't do this the file that might already be disc cached will be deleted from the cache
73
+ newUrl.searchParams.append("range", "true");
74
+ url = newUrl.toString();
75
+ }
76
+ const header = await fetch(url, {
69
77
  method: "GET",
70
78
  headers: {
71
79
  "range": "bytes=0-32"
@@ -697,7 +697,7 @@ export class NeedleMenuElement extends HTMLElement {
697
697
  if (res == true && hasCommercialLicense() && !debugNonCommercial) {
698
698
  let visible = this._userRequestedLogoVisible;
699
699
  if (visible === undefined) visible = false;
700
- this.#onSetLogoVisible(visible);
700
+ this.___onSetLogoVisible(visible);
701
701
  }
702
702
  }));
703
703
  } catch (e) {
@@ -827,10 +827,10 @@ export class NeedleMenuElement extends HTMLElement {
827
827
  if (!localNetwork) return;
828
828
  }
829
829
  }
830
- this.#onSetLogoVisible(visible);
830
+ this.___onSetLogoVisible(visible);
831
831
  }
832
832
 
833
- #onSetLogoVisible(visible: boolean) {
833
+ private ___onSetLogoVisible(visible: boolean) {
834
834
  this.logoContainer.style.display = "";
835
835
  this.logoContainer.style.opacity = "1";
836
836
  this.logoContainer.style.visibility = "visible";
@@ -1,4 +1,4 @@
1
- import { EquirectangularReflectionMapping, Euler, Frustum, Matrix4, OrthographicCamera, PerspectiveCamera, Ray, Vector3 } from "three";
1
+ import { Color, EquirectangularReflectionMapping, Euler, Frustum, Matrix4, OrthographicCamera, PerspectiveCamera, Ray, Vector3 } from "three";
2
2
  import { Texture } from "three";
3
3
 
4
4
  import { showBalloonMessage } from "../engine/debug/index.js";
@@ -289,15 +289,18 @@ export class Camera extends Behaviour implements ICamera {
289
289
  public get backgroundColor(): RGBAColor | null {
290
290
  return this._backgroundColor ?? null;
291
291
  }
292
- public set backgroundColor(val: RGBAColor | null) {
292
+ public set backgroundColor(val: RGBAColor | Color | null) {
293
293
  if (!val) return;
294
294
  if (!this._backgroundColor) {
295
- if (!val.clone) return;
296
- this._backgroundColor = val.clone();
295
+ this._backgroundColor = new RGBAColor(1, 1, 1, 1);
297
296
  }
298
- else this._backgroundColor.copy(val);
297
+
298
+ this._backgroundColor.copy(val);
299
+
299
300
  // set background color to solid if provided color doesnt have any alpha channel
300
- if (val.alpha === undefined) this._backgroundColor.alpha = 1;
301
+ if ((!("alpha" in val) || val.alpha === undefined)) {
302
+ this._backgroundColor.alpha = 1;
303
+ }
301
304
  this.applyClearFlagsIfIsActiveCamera();
302
305
  }
303
306
 
@@ -1,15 +1,17 @@
1
- import { PerspectiveCamera } from "three";
1
+ import { Color, PerspectiveCamera, PMREMGenerator } from "three";
2
2
 
3
3
  import { getCameraController } from "../engine/engine_camera.js";
4
4
  import { addNewComponent, getOrAddComponent } from "../engine/engine_components.js";
5
5
  import { Context } from "../engine/engine_context.js";
6
6
  import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
7
7
  import { NeedleEngineHTMLElement } from "../engine/engine_element.js";
8
+ import { createFlatTexture, createTrilightTexture } from "../engine/engine_shaders.js";
8
9
  import type { ICamera, IContext } from "../engine/engine_types.js";
9
10
  import { getParam } from "../engine/engine_utils.js";
10
11
  import { RGBAColor } from "../engine/js-extensions/index.js";
11
12
  import { Camera, ClearFlags } from "./Camera.js";
12
13
  import { OrbitControls } from "./OrbitControls.js";
14
+ import EnvironmentScene from "./utils/EnvironmentScene.js";
13
15
 
14
16
  const debug = getParam("debugmissingcamera");
15
17
 
@@ -30,27 +32,47 @@ ContextRegistry.registerCallback(ContextEvent.MissingCamera, (evt) => {
30
32
 
31
33
  const camInstance = new Camera();
32
34
  camInstance.sourceId = evt.files?.[0]?.src ?? "unknown"
35
+ camInstance.fieldOfView = 35;
33
36
 
37
+ const transparentAttribute = evt.context.domElement.getAttribute("transparent");
38
+ if (transparentAttribute != undefined) {
39
+ camInstance.clearFlags = ClearFlags.Uninitialized;
40
+ }
34
41
  // Set the clearFlags to a skybox if we have one OR if the user set a skybox image attribute
35
- if (evt.context.domElement.getAttribute("skybox-image")?.length || evt.context.domElement.getAttribute("background-image")?.length || (evt.context as Context).lightmaps.tryGetSkybox(camInstance.sourceId)) {
42
+ else if (evt.context.domElement.getAttribute("skybox-image")?.length || evt.context.domElement.getAttribute("background-image")?.length || (evt.context as Context).lightmaps.tryGetSkybox(camInstance.sourceId)) {
36
43
  camInstance.clearFlags = ClearFlags.Skybox;
44
+ // TODO: can we store the backgroundBlurriness in the gltf file somewhere except inside the camera?
45
+ // e.g. when we export a scene from blender without a camera in the scene
46
+ camInstance.backgroundBlurriness = .2; // same as in blender 0.5
37
47
  }
38
- else
39
- // TODO provide a nice default skybox
48
+ else {
40
49
  camInstance.clearFlags = ClearFlags.SolidColor;
41
- camInstance.backgroundColor = new RGBAColor(0.5, 0.5, 0.5, 1);
42
- camInstance.fieldOfView = 35;
43
50
 
44
- const transparentAttribute = evt.context.domElement.getAttribute("transparent");
45
- if (transparentAttribute != undefined) {
46
- camInstance.clearFlags = ClearFlags.Uninitialized;
51
+ let backgroundColor = "#efefef";
52
+ if(typeof window !== undefined && (window.matchMedia('(prefers-color-scheme: dark)').matches)) {
53
+ backgroundColor = "#1f1f1f";
54
+ }
55
+ scene.background = new Color(backgroundColor); // dont set it on the camera because this might be controlled from "background-color" attribute which is set on the scene directly. If the camera has a background color, it will override the scene's background color
56
+
57
+ // Generate a default environment map if none is set
58
+ if (!scene.environment) {
59
+ // const backgroundColorAttribute = evt.context.domElement.getAttribute("background-color") ?? "#fff";
60
+ // const backgroundColor = new Color(backgroundColorAttribute);
61
+ const pmremGenerator = new PMREMGenerator(evt.context.renderer);
62
+ const env = new EnvironmentScene("neutral");
63
+ // const background = scene.background;
64
+ // const col0 = new Color(.1, .1, .1);
65
+ // const col1 = new Color(.3, .3, .3);
66
+ // const col2 = new Color(1, 1, 1);
67
+ // const envmap = createTrilightTexture(col0, col1, col2, 32, 32);
68
+ // scene.background = envmap;
69
+ scene.environment = pmremGenerator.fromScene(env, .025).texture;
70
+ // scene.background = background;
71
+ }
47
72
  }
48
73
 
49
- // TODO: can we store the backgroundBlurriness in the gltf file somewhere except inside the camera?
50
- // e.g. when we export a scene from blender without a camera in the scene
51
- camInstance.backgroundBlurriness = .2; // same as in blender 0.5
52
- const cam = addNewComponent(cameraObject, camInstance, true) as ICamera;
53
74
 
75
+ const cam = addNewComponent(cameraObject, camInstance, true) as ICamera;
54
76
  cameraObject.position.x = 0;
55
77
  cameraObject.position.y = 1;
56
78
  cameraObject.position.z = 2;
@@ -1128,7 +1128,8 @@ export abstract class Component implements IComponent, EventTarget,
1128
1128
  }
1129
1129
 
1130
1130
  /**
1131
- * Gets the position of this component's GameObject in world space
1131
+ * Gets the position of this component's GameObject in world space.
1132
+ * Note: This is equivalent to calling `this.gameObject.worldPosition`
1132
1133
  */
1133
1134
  get worldPosition(): Vector3 {
1134
1135
  return threeutils.getWorldPosition(this.gameObject);
@@ -1154,6 +1155,7 @@ export abstract class Component implements IComponent, EventTarget,
1154
1155
 
1155
1156
  /**
1156
1157
  * Gets the rotation of this component's GameObject in world space as a quaternion
1158
+ * Note: This is equivalent to calling `this.gameObject.worldQuaternion`
1157
1159
  */
1158
1160
  get worldQuaternion(): Quaternion {
1159
1161
  return threeutils.getWorldQuaternion(this.gameObject);
@@ -1194,7 +1196,8 @@ export abstract class Component implements IComponent, EventTarget,
1194
1196
  }
1195
1197
 
1196
1198
  /**
1197
- * Gets the rotation of this component's GameObject in world space as Euler angles (in degrees)
1199
+ * Gets the rotation of this component's GameObject in world space as Euler angles (in degrees)
1200
+ * Note: This is equivalent to calling `this.gameObject.worldRotation`
1198
1201
  */
1199
1202
  get worldRotation(): Vector3 {
1200
1203
  return this.gameObject.worldRotation;
@@ -17,9 +17,15 @@ import { Behaviour, GameObject } from "./Component.js";
17
17
  const debug = getParam("debugcontactshadows");
18
18
 
19
19
  onStart(ctx => {
20
- const val = ctx.domElement.getAttribute("contactshadows");
20
+ const val = ctx.domElement.getAttribute("contactshadows") || ctx.domElement.getAttribute("contact-shadows");
21
21
  if (val != undefined && val != "0" && val != "false") {
22
- ContactShadows.auto(ctx);
22
+ console.debug("Auto-creating ContactShadows because of `contactshadows` attribute");
23
+ const shadows = ContactShadows.auto(ctx);
24
+ const intensity = parseFloat(val);
25
+ if (!isNaN(intensity)) {
26
+ shadows.opacity = intensity;
27
+ shadows.darkness = intensity;
28
+ }
23
29
  }
24
30
  })
25
31
 
@@ -316,6 +316,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
316
316
  if (DeviceUtilities.isMobileDevice()) this.doubleClickToFocus = true;
317
317
  }
318
318
  this._controls.addEventListener("start", this.onControlsChangeStarted);
319
+ this._controls.addEventListener("end", this.onControlsChangeEnded);
319
320
 
320
321
  if (!this._startedListeningToKeyEvents && this.enableKeys) {
321
322
  this._startedListeningToKeyEvents = true;
@@ -346,6 +347,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
346
347
  this._controls.enabled = false;
347
348
  this._controls.autoRotate = false;
348
349
  this._controls.removeEventListener("start", this.onControlsChangeStarted);
350
+ this._controls.removeEventListener("end", this.onControlsChangeEnded);
349
351
  try {
350
352
  this._controls.stopListenToKeyEvents();
351
353
  } catch { /** this fails if we never listened to key events... */ }
@@ -366,7 +368,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
366
368
  this._activePointerEvents.push(_evt);
367
369
  }
368
370
  private _onPointerDownLate = (evt: NEPointerEvent) => {
369
- if(evt.used && this._controls) {
371
+ if (evt.used && this._controls) {
370
372
  // Disabling orbit controls here because otherwise we get a slight movement when e.g. using DragControls
371
373
  this._controls.enabled = false;
372
374
  }
@@ -413,22 +415,45 @@ export class OrbitControls extends Behaviour implements ICameraController {
413
415
  this.setTargetFromRaycast();
414
416
  }
415
417
  // Automatically update the camera focus
416
- else if (!evt.used && this.autoTarget) {
417
- const ray = new Ray(this._cameraObject?.worldPosition, this._cameraObject?.worldForward.multiplyScalar(-1));
418
- const hits = this.context.physics.raycastFromRay(ray);
419
- const hit = hits.length > 0 ? hits[0] : undefined;
420
- if(hit && hit.distance > this.minZoom && hit.distance < this.maxZoom) {
421
- if(debug) Gizmos.DrawWireSphere(hit.point, 0.1, 0xff0000, 2);
422
- this._controls?.target.copy(hits[0].point);
423
- }
424
- }
418
+ // else if (!evt.used && this.autoTarget) {
419
+ // this.updateTargetNow();
420
+ // }
425
421
  };
426
422
 
423
+ private updateTargetNow() {
424
+ const ray = new Ray(this._cameraObject?.worldPosition, this._cameraObject?.worldForward.multiplyScalar(-1));
425
+ const hits = this.context.physics.raycastFromRay(ray);
426
+ const hit = hits.length > 0 ? hits[0] : undefined;
427
+ if (hit && hit.distance > this.minZoom && hit.distance < this.maxZoom) {
428
+ if (debug) Gizmos.DrawWireSphere(hit.point, 0.1, 0xff0000, 2);
429
+ this._controls?.target.copy(hits[0].point);
430
+ }
431
+ }
432
+
433
+ private _orbitStartAngle: number = 0;
427
434
  private onControlsChangeStarted = () => {
435
+ if (this._controls) {
436
+ this._orbitStartAngle = this._controls.getAzimuthalAngle() + this._controls.getPolarAngle();
437
+ }
428
438
  if (this._syncedTransform) {
429
439
  this._syncedTransform.requestOwnership();
430
440
  }
431
441
  }
442
+ private onControlsChangeEnded = () => {
443
+
444
+ if (this._controls) {
445
+ if (this.autoTarget) {
446
+ const newAngle = this._controls.getAzimuthalAngle() + this._controls.getPolarAngle();
447
+ const delta = newAngle - this._orbitStartAngle;
448
+ if (Math.abs(delta) < .01) {
449
+ if (debug) console.debug("OrbitControls: No movement detected, updating target now");
450
+ this.updateTargetNow();
451
+ }
452
+ else if(debug) console.debug("OrbitControls: Movement detected", delta);
453
+ }
454
+ }
455
+
456
+ }
432
457
 
433
458
  private _shouldDisable: boolean = false;
434
459
  private afterHandleInput(evt: CustomEvent<AfterHandleInputEvent>) {
@@ -51,7 +51,7 @@ onUpdate((ctx) => {
51
51
  const threshold = isRunningOnGlitch ? 10 : 40;
52
52
  FAST_INTERVAL = Math.floor(FAST_ACTIVE_SYNCTRANSFORMS / threshold);
53
53
  FAST_ACTIVE_SYNCTRANSFORMS = 0;
54
- if(debug && FAST_INTERVAL > 0) console.log("Sync Transform Fast Interval", FAST_INTERVAL);
54
+ if (debug && FAST_INTERVAL > 0) console.log("Sync Transform Fast Interval", FAST_INTERVAL);
55
55
  })
56
56
 
57
57
  /**
@@ -62,20 +62,20 @@ onUpdate((ctx) => {
62
62
  */
63
63
  export class SyncedTransform extends Behaviour {
64
64
 
65
-
65
+
66
66
  // public autoOwnership: boolean = true;
67
67
  /** When true, overrides physics behavior when this object is owned by the local user */
68
68
  public overridePhysics: boolean = true
69
-
69
+
70
70
  /** Whether to smoothly interpolate position changes when receiving updates */
71
71
  public interpolatePosition: boolean = true;
72
-
72
+
73
73
  /** Whether to smoothly interpolate rotation changes when receiving updates */
74
74
  public interpolateRotation: boolean = true;
75
-
75
+
76
76
  /** When true, sends updates at a higher frequency, useful for fast-moving objects */
77
77
  public fastMode: boolean = false;
78
-
78
+
79
79
  /** When true, notifies other clients when this object is destroyed */
80
80
  public syncDestroy: boolean = false;
81
81
 
@@ -135,8 +135,9 @@ export class SyncedTransform extends Behaviour {
135
135
  this._targetRotation = new Quaternion();
136
136
 
137
137
  // sync instantiate issue was because they shared the same last pos vector!
138
- this.lastWorldPos = new Vector3();
139
- this.lastWorldRotation = new Quaternion();
138
+ this.lastPosition = new Vector3();
139
+ this.lastRotation = new Quaternion();
140
+ this.lastScale = new Vector3();
140
141
 
141
142
  this.rb = GameObject.getComponentInChildren(this.gameObject, Rigidbody);
142
143
  if (this.rb) {
@@ -207,6 +208,11 @@ export class SyncedTransform extends Behaviour {
207
208
  if (!this.interpolateRotation || !this._receivedDataBefore)
208
209
  setWorldEuler(this.gameObject, this.tempEuler);
209
210
  }
211
+
212
+ const scale = transform.scale();
213
+ if (scale) {
214
+ this.gameObject.scale.set(scale.x(), scale.y(), scale.z());
215
+ }
210
216
  }
211
217
  this._receivedDataBefore = true;
212
218
 
@@ -221,8 +227,9 @@ export class SyncedTransform extends Behaviour {
221
227
  * Initializes tracking of position and rotation when component is enabled
222
228
  */
223
229
  onEnable(): void {
224
- this.lastWorldPos.copy(this.worldPosition);
225
- this.lastWorldRotation.copy(this.worldQuaternion);
230
+ this.lastPosition.copy(this.worldPosition);
231
+ this.lastRotation.copy(this.worldQuaternion);
232
+ this.lastScale.copy(this.gameObject.scale);
226
233
  this._needsUpdate = true;
227
234
  // console.log("ENABLE", this.guid, this.gameObject.guid, this.lastWorldPos);
228
235
  if (this._model) {
@@ -241,8 +248,9 @@ export class SyncedTransform extends Behaviour {
241
248
 
242
249
 
243
250
  private receivedUpdate = false;
244
- private lastWorldPos!: Vector3;
245
- private lastWorldRotation!: Quaternion;
251
+ private lastPosition!: Vector3;
252
+ private lastRotation!: Quaternion;
253
+ private lastScale!: Vector3;
246
254
 
247
255
  /**
248
256
  * @internal
@@ -264,24 +272,27 @@ export class SyncedTransform extends Behaviour {
264
272
  this._model.requestOwnership();
265
273
  }
266
274
 
267
- const wp = this.worldPosition;
268
- const wr = this.worldQuaternion;
275
+ const pos = this.worldPosition;
276
+ const rot = this.worldQuaternion;
277
+ const scale = this.gameObject.scale;
269
278
  if (this._model.isOwned && !this.receivedUpdate) {
270
- const worlddiff = wp.distanceTo(this.lastWorldPos);
271
- const worldRot = wr.angleTo(this.lastWorldRotation);
272
279
  const threshold = this._model.hasOwnership || this.fastMode ? .0001 : .001;
273
- if (worlddiff > threshold || worldRot > threshold) {
280
+ if (pos.distanceTo(this.lastPosition) > threshold ||
281
+ rot.angleTo(this.lastRotation) > threshold ||
282
+ scale.distanceTo(this.lastScale) > threshold) {
274
283
  // console.log(worlddiff, worldRot);
275
284
  if (!this._model.hasOwnership) {
276
285
 
277
286
  if (debug)
278
- console.log(this.guid, "reset because not owned but", this.gameObject.name, this.lastWorldPos);
287
+ console.log(this.guid, "reset because not owned but", this.gameObject.name, this.lastPosition);
279
288
 
280
- this.worldPosition = this.lastWorldPos;
281
- wp.copy(this.lastWorldPos);
289
+ this.worldPosition = this.lastPosition;
290
+ pos.copy(this.lastPosition);
282
291
 
283
- this.worldQuaternion = this.lastWorldRotation;
284
- wr.copy(this.lastWorldRotation);
292
+ this.worldQuaternion = this.lastRotation;
293
+ rot.copy(this.lastRotation);
294
+
295
+ this.gameObject.scale.copy(this.lastScale);
285
296
 
286
297
  InstancingUtil.markDirty(this.gameObject, true);
287
298
  this._needsUpdate = false;
@@ -323,12 +334,13 @@ export class SyncedTransform extends Behaviour {
323
334
 
324
335
 
325
336
  this.receivedUpdate = false;
326
- this.lastWorldPos.copy(wp);
327
- this.lastWorldRotation.copy(wr);
337
+ this.lastPosition.copy(pos);
338
+ this.lastRotation.copy(rot);
339
+ this.lastScale.copy(scale);
328
340
 
329
341
 
330
342
  if (!this._model) return;
331
-
343
+
332
344
  if (!this._model || this._model.hasOwnership === undefined || !this._model.hasOwnership) {
333
345
  // if we're not the owner of this synced transform then don't send any data
334
346
  return;
@@ -281,8 +281,12 @@ export class PostProcessingHandler {
281
281
 
282
282
  if (this._passIndices !== null) {
283
283
  const newPasses = [composer.passes[0]];
284
- if (this._passIndices.length > 0 && this._passIndices[0] !== 0) {
285
- newPasses.push(...this._passIndices.map(index => composer.passes[index]).filter(pass => pass));
284
+ if (this._passIndices.length > 0) {
285
+ newPasses.push(...this._passIndices
286
+ .filter(x => x !== 0)
287
+ .map(index => composer.passes[index])
288
+ .filter(pass => pass)
289
+ );
286
290
  }
287
291
  if (newPasses.length > 0) {
288
292
  console.log("[PostProcessing] Passes (selected) →", newPasses);
@@ -290,6 +294,7 @@ export class PostProcessingHandler {
290
294
  composer.passes.length = 0;
291
295
  for (const pass of newPasses) {
292
296
  pass.enabled = true;
297
+ pass.renderToScreen = false; // allows automatic setting for the last pass
293
298
  composer.addPass(pass);
294
299
  }
295
300
  }