@needle-tools/engine 4.10.0-next.55c0bf9 → 4.10.0-next.870425c

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 (142) hide show
  1. package/CHANGELOG.md +8 -3
  2. package/README.md +2 -1
  3. package/components.needle.json +1 -1
  4. package/dist/{needle-engine.bundle-Cf5H9Zy9.umd.cjs → needle-engine.bundle-0b6rexDr.umd.cjs} +154 -141
  5. package/dist/{needle-engine.bundle-CUo74dPe.js → needle-engine.bundle-B5GtGvbq.js} +8329 -8050
  6. package/dist/needle-engine.bundle-CicGQeCY.min.js +1652 -0
  7. package/dist/needle-engine.js +259 -257
  8. package/dist/needle-engine.min.js +1 -1
  9. package/dist/needle-engine.umd.cjs +1 -1
  10. package/dist/vendor-CPuBPspY.umd.cjs +1121 -0
  11. package/dist/vendor-DPCU8cUF.min.js +1121 -0
  12. package/dist/vendor-MBoqSyFm.js +16240 -0
  13. package/lib/engine/codegen/register_types.js +2 -0
  14. package/lib/engine/codegen/register_types.js.map +1 -1
  15. package/lib/engine/engine_camera.d.ts +7 -1
  16. package/lib/engine/engine_camera.fit.d.ts +1 -1
  17. package/lib/engine/engine_camera.fit.js +3 -30
  18. package/lib/engine/engine_camera.fit.js.map +1 -1
  19. package/lib/engine/engine_camera.js +46 -6
  20. package/lib/engine/engine_camera.js.map +1 -1
  21. package/lib/engine/engine_context.d.ts +6 -0
  22. package/lib/engine/engine_context.js +48 -9
  23. package/lib/engine/engine_context.js.map +1 -1
  24. package/lib/engine/engine_gizmos.d.ts +11 -10
  25. package/lib/engine/engine_gizmos.js +24 -10
  26. package/lib/engine/engine_gizmos.js.map +1 -1
  27. package/lib/engine/engine_license.js +1 -1
  28. package/lib/engine/engine_license.js.map +1 -1
  29. package/lib/engine/engine_lightdata.d.ts +3 -3
  30. package/lib/engine/engine_lightdata.js +10 -10
  31. package/lib/engine/engine_lightdata.js.map +1 -1
  32. package/lib/engine/engine_physics_rapier.js +4 -0
  33. package/lib/engine/engine_physics_rapier.js.map +1 -1
  34. package/lib/engine/engine_scenelighting.d.ts +1 -1
  35. package/lib/engine/engine_scenelighting.js +4 -5
  36. package/lib/engine/engine_scenelighting.js.map +1 -1
  37. package/lib/engine/engine_utils.d.ts +3 -1
  38. package/lib/engine/engine_utils.js +11 -0
  39. package/lib/engine/engine_utils.js.map +1 -1
  40. package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
  41. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  42. package/lib/engine/extensions/extension_utils.js +1 -1
  43. package/lib/engine/extensions/extension_utils.js.map +1 -1
  44. package/lib/engine/webcomponents/logo-element.d.ts +1 -1
  45. package/lib/engine/webcomponents/logo-element.js +29 -5
  46. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  47. package/lib/engine/webcomponents/needle menu/needle-menu.js +4 -3
  48. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  49. package/lib/engine/webcomponents/needle-engine.js +22 -0
  50. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  51. package/lib/engine/webcomponents/needle-engine.loading.d.ts +0 -1
  52. package/lib/engine/webcomponents/needle-engine.loading.js +3 -36
  53. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  54. package/lib/engine/xr/NeedleXRController.d.ts +3 -3
  55. package/lib/engine/xr/NeedleXRController.js +28 -0
  56. package/lib/engine/xr/NeedleXRController.js.map +1 -1
  57. package/lib/engine-components/Camera.d.ts +1 -1
  58. package/lib/engine-components/Camera.js +1 -1
  59. package/lib/engine-components/CameraUtils.js +2 -1
  60. package/lib/engine-components/CameraUtils.js.map +1 -1
  61. package/lib/engine-components/CharacterController.d.ts +2 -2
  62. package/lib/engine-components/CharacterController.js +2 -2
  63. package/lib/engine-components/OrbitControls.d.ts +5 -2
  64. package/lib/engine-components/OrbitControls.js +31 -7
  65. package/lib/engine-components/OrbitControls.js.map +1 -1
  66. package/lib/engine-components/Renderer.js +6 -1
  67. package/lib/engine-components/Renderer.js.map +1 -1
  68. package/lib/engine-components/Skybox.js +22 -4
  69. package/lib/engine-components/Skybox.js.map +1 -1
  70. package/lib/engine-components/codegen/components.d.ts +1 -0
  71. package/lib/engine-components/codegen/components.js +1 -0
  72. package/lib/engine-components/codegen/components.js.map +1 -1
  73. package/lib/engine-components/debug/LogStats.d.ts +1 -0
  74. package/lib/engine-components/debug/LogStats.js +1 -0
  75. package/lib/engine-components/debug/LogStats.js.map +1 -1
  76. package/lib/engine-components/timeline/PlayableDirector.d.ts +7 -0
  77. package/lib/engine-components/timeline/PlayableDirector.js +8 -1
  78. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  79. package/lib/engine-components/timeline/TimelineModels.d.ts +45 -2
  80. package/lib/engine-components/timeline/TimelineModels.js +6 -0
  81. package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
  82. package/lib/engine-components/timeline/TimelineTracks.d.ts +2 -1
  83. package/lib/engine-components/timeline/TimelineTracks.js +30 -25
  84. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  85. package/lib/engine-components/utils/LookAt.js +5 -1
  86. package/lib/engine-components/utils/LookAt.js.map +1 -1
  87. package/lib/engine-components/web/Clickthrough.js +10 -2
  88. package/lib/engine-components/web/Clickthrough.js.map +1 -1
  89. package/lib/engine-components/web/ScrollFollow.d.ts +24 -0
  90. package/lib/engine-components/web/ScrollFollow.js +167 -41
  91. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  92. package/lib/engine-components/web/ViewBox.d.ts +46 -0
  93. package/lib/engine-components/web/ViewBox.js +270 -0
  94. package/lib/engine-components/web/ViewBox.js.map +1 -0
  95. package/lib/engine-components/web/index.d.ts +1 -0
  96. package/lib/engine-components/web/index.js +1 -0
  97. package/lib/engine-components/web/index.js.map +1 -1
  98. package/lib/engine-components/webxr/WebARSessionRoot.js +1 -0
  99. package/lib/engine-components/webxr/WebARSessionRoot.js.map +1 -1
  100. package/lib/engine-components-experimental/Presentation.d.ts +1 -0
  101. package/lib/engine-components-experimental/Presentation.js +1 -0
  102. package/lib/engine-components-experimental/Presentation.js.map +1 -1
  103. package/package.json +2 -1
  104. package/src/engine/codegen/register_types.ts +2 -0
  105. package/src/engine/engine_camera.fit.ts +2 -32
  106. package/src/engine/engine_camera.ts +61 -9
  107. package/src/engine/engine_context.ts +50 -10
  108. package/src/engine/engine_gizmos.ts +37 -23
  109. package/src/engine/engine_license.ts +1 -1
  110. package/src/engine/engine_lightdata.ts +11 -11
  111. package/src/engine/engine_physics_rapier.ts +3 -0
  112. package/src/engine/engine_scenelighting.ts +5 -6
  113. package/src/engine/engine_utils.ts +12 -0
  114. package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
  115. package/src/engine/extensions/extension_utils.ts +1 -1
  116. package/src/engine/webcomponents/logo-element.ts +29 -4
  117. package/src/engine/webcomponents/needle menu/needle-menu.ts +4 -3
  118. package/src/engine/webcomponents/needle-engine.loading.ts +32 -32
  119. package/src/engine/webcomponents/needle-engine.ts +33 -6
  120. package/src/engine/xr/NeedleXRController.ts +36 -4
  121. package/src/engine-components/Camera.ts +1 -1
  122. package/src/engine-components/CameraUtils.ts +1 -1
  123. package/src/engine-components/CharacterController.ts +2 -2
  124. package/src/engine-components/OrbitControls.ts +41 -2
  125. package/src/engine-components/Renderer.ts +6 -1
  126. package/src/engine-components/Skybox.ts +26 -7
  127. package/src/engine-components/codegen/components.ts +1 -0
  128. package/src/engine-components/debug/LogStats.ts +1 -0
  129. package/src/engine-components/timeline/PlayableDirector.ts +10 -1
  130. package/src/engine-components/timeline/TimelineModels.ts +45 -3
  131. package/src/engine-components/timeline/TimelineTracks.ts +30 -25
  132. package/src/engine-components/utils/LookAt.ts +5 -1
  133. package/src/engine-components/web/Clickthrough.ts +11 -2
  134. package/src/engine-components/web/ScrollFollow.ts +200 -48
  135. package/src/engine-components/web/ViewBox.ts +292 -0
  136. package/src/engine-components/web/index.ts +2 -1
  137. package/src/engine-components/webxr/WebARSessionRoot.ts +1 -0
  138. package/src/engine-components-experimental/Presentation.ts +1 -0
  139. package/dist/needle-engine.bundle-DlAVTipB.min.js +0 -1639
  140. package/dist/vendor-D0Yvltn9.umd.cjs +0 -1121
  141. package/dist/vendor-DU8tJyl_.js +0 -14366
  142. package/dist/vendor-JyrX4DVM.min.js +0 -1121
@@ -38,14 +38,18 @@ const observedAttributes = [
38
38
  "loadstart",
39
39
  "progress",
40
40
  "loadfinished",
41
+
41
42
  "dracoDecoderPath",
42
43
  "dracoDecoderType",
43
44
  "ktx2DecoderPath",
45
+
44
46
  "tone-mapping",
45
47
  "tone-mapping-exposure",
46
48
  "background-blurriness",
47
49
  "background-color",
48
50
  "environment-intensity",
51
+
52
+ "focus-rect",
49
53
  ]
50
54
 
51
55
  // https://developers.google.com/web/fundamentals/web-components/customelements
@@ -95,7 +99,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
95
99
  if (value === null) this.removeAttribute("camera-controls");
96
100
  else this.setAttribute("camera-controls", value ? "true" : "false");
97
101
  }
98
-
102
+
99
103
 
100
104
  /**
101
105
  * Get the current context for this web component instance. The context is created when the src attribute is set and the loading has finished.
@@ -331,17 +335,17 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
331
335
  if (debug) console.log("ktx2DecoderPath", newValue);
332
336
  setKtx2TranscoderPath(newValue);
333
337
  break;
334
-
338
+
335
339
  case "tonemapping":
336
340
  case "tone-mapping":
337
341
  case "tone-mapping-exposure":
338
342
  case "background-blurriness":
339
343
  case "background-color":
340
344
  case "environment-intensity":
341
- {
342
- this.applyAttributes();
343
- break;
344
- }
345
+ {
346
+ this.applyAttributes();
347
+ break;
348
+ }
345
349
  case "public-key": {
346
350
  if (newValue != PUBLIC_KEY)
347
351
  this.setPublicKey();
@@ -352,6 +356,25 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
352
356
  this.setVersion();
353
357
  break;
354
358
  }
359
+
360
+ case "focus-rect":
361
+ {
362
+ const focus_rect = this.getAttribute("focus-rect") as HTMLElement | string | null;
363
+ if (focus_rect && this._context) {
364
+ if (focus_rect === null) {
365
+ this._context.setCameraFocusRect(null);
366
+ }
367
+ else if (typeof focus_rect === "string" && focus_rect.length > 0) {
368
+ const element = document.querySelector(focus_rect);
369
+ this._context.setCameraFocusRect(element instanceof HTMLElement ? element : null);
370
+ }
371
+ else if (focus_rect instanceof HTMLElement) {
372
+ this._context.setCameraFocusRect(focus_rect);
373
+ }
374
+
375
+ }
376
+ }
377
+ break;
355
378
  }
356
379
  }
357
380
 
@@ -570,6 +593,10 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
570
593
  this._context.renderer.setClearColor(rgbaColor, rgbaColor.alpha);
571
594
  this.context.scene.background = null;
572
595
  }
596
+ // HACK: if we set background-color to a color and then back to null we want the background-image attribute to re-apply
597
+ else if (this.getAttribute("background-image")) {
598
+ this.setAttribute("background-image", this.getAttribute("background-image")!);
599
+ }
573
600
  }
574
601
  }
575
602
 
@@ -23,10 +23,10 @@ const debugCustomGesture = getParam("debugcustomgesture");
23
23
  // let _didReceiveSelectStartEvent = false;
24
24
 
25
25
  // https://github.com/immersive-web/webxr-input-profiles/blob/4484a05e30bcd43fe86bb4e06b7a707861a26796/packages/registry/profiles/meta/meta-quest-touch-plus.json
26
- declare type ControllerAxes = "xr-standard-thumbstick";
27
- declare type StickName = "xr-standard-thumbstick";
26
+ declare type ControllerAxes = "xr-standard-thumbstick" | "xr-standard-touchpad";
27
+ declare type StickName = "xr-standard-thumbstick" | "xr-standard-touchpad";
28
28
  declare type Mapping = "xr-standard";
29
- declare type ComponentType = "button" | "thumbstick" | "squeeze";
29
+ declare type ComponentType = "button" | "thumbstick" | "squeeze" | "touchpad";
30
30
  declare type GamepadKey = "button" | "xAxis" | "yAxis";
31
31
 
32
32
  declare type NeedleXRControllerButtonName = ButtonName | "primary-button" | "primary";
@@ -407,6 +407,16 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
407
407
  gamepadStr += "\n[axes " + gp.axes.length + "]: " + gp.axes.map(a => a.toPrecision(1)).join(",");
408
408
  debugStr += "\n" + gamepadStr;
409
409
  }
410
+ if (this._layout) {
411
+ debugStr += "\nLayout: ";
412
+ for (const component of Object.keys(this._layout.components || {})) {
413
+ const val = this.getStick(component as StickName);
414
+ const indices = this._layout.components[component]?.gamepadIndices;
415
+ const indicesAsString = indices ? Object.entries(indices).map(e => e[0][0].toUpperCase() + e[0].slice(1) + "=" + e[1]).join(",") : "";
416
+ debugStr += `\n ${component}: ${this._layout.components[component]?.type} [${indicesAsString}] (${val.x.toPrecision(2)},${val.y.toPrecision(2)})`;
417
+ }
418
+ }
419
+
410
420
  Gizmos.DrawLabel(debugLabelPosition, debugStr, .006);
411
421
  }
412
422
 
@@ -730,6 +740,7 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
730
740
  if (componentModel?.gamepadIndices) {
731
741
  switch (componentModel.type) {
732
742
  case "thumbstick":
743
+ case "touchpad":
733
744
  if (this.inputSource.gamepad) {
734
745
  const xIndex = componentModel.gamepadIndices!.xAxis!;
735
746
  const yIndex = componentModel.gamepadIndices!.yAxis!;
@@ -760,7 +771,11 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
760
771
  this._isMetaQuestTouchController = this.profiles.includes("meta-quest-touch-plus") || this.profiles.includes("oculus-touch-v3");
761
772
 
762
773
  // Proper profile starting with v69 and browser 35.1
763
- this._isMxInk = this.profiles.includes("logitech-mx-ink")
774
+ this._isMxInk = this.profiles.includes("logitech-mx-ink");
775
+
776
+ // For debugging to see ALL available profiles
777
+ /** @ts-ignore */
778
+ // fetchProfilesList(DEFAULT_PROFILES_PATH).then(list => console.log("Available controller profiles", list));
764
779
 
765
780
  if (!this._layout) {
766
781
  // Ignore transient-pointer since we likely don't want to spawn a controller visual just for a temporary pointer.
@@ -780,6 +795,8 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
780
795
  res.assetPath || ""
781
796
  );
782
797
 
798
+ // const overrideProfile = await fetch(DEFAULT_PROFILES_PATH + "/htc-vive-focus-3/profile.json").then(r => r.json());
799
+
783
800
  const profile = res.profile as InputDeviceProfile;
784
801
  const layout = profile.layouts[this.inputSource.handedness];
785
802
  this._layout = layout;
@@ -791,6 +808,21 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
791
808
  this._layout.gamepad[component.gamepadIndices!.button!] = key as XRControllerButtonName;
792
809
  }
793
810
  }
811
+
812
+ // If we have 4 axes and no thumbstick defined, we define thumbstick for axis 3+4
813
+ // This is a workaround for HTC Vive Focus 3 controllers, which have the profile for Vive Focus Plus...
814
+ // This workaround fixes it for HTC Vive Focus 3 but does not change anything for Vive Focus Plus controllers
815
+ if (this.profiles.length >= 1 && this.profiles[0] === "htc-vive-focus-plus") {
816
+ if (this.inputSource.gamepad && this.inputSource.gamepad.axes.length === 4 && !this._layout.components["xr-standard-thumbstick"]) {
817
+ this._layout.components["xr-standard-thumbstick"] = {
818
+ type: "thumbstick",
819
+ gamepadIndices: {
820
+ xAxis: 2,
821
+ yAxis: 3,
822
+ }
823
+ }
824
+ }
825
+ }
794
826
  }
795
827
  // if (debug) console.log(this._layout, this.inputSource);
796
828
  // debugger;
@@ -36,7 +36,7 @@ const debugscreenpointtoray = getParam("debugscreenpointtoray");
36
36
  * Supports both perspective and orthographic cameras with various rendering options.
37
37
  * Internally, this component uses {@link PerspectiveCamera} and {@link OrthographicCamera} three.js objects.
38
38
  *
39
- * @category Camera Controls
39
+ * @category Camera
40
40
  * @group Components
41
41
  */
42
42
  export class Camera extends Behaviour implements ICamera {
@@ -122,7 +122,7 @@ function createDefaultCameraControls(context: IContext, cam?: ICamera) {
122
122
  orbit.autoRotate = autoRotate != "0" && autoRotate?.toLowerCase() != "false";
123
123
  const autoRotateSpeed = Number.parseFloat(autoRotate || ".5");
124
124
  orbit.autoRotateSpeed = !isNaN(autoRotateSpeed) ? autoRotateSpeed : .5;
125
- console.log("Auto-rotate", orbit.autoRotate, "speed:", orbit.autoRotateSpeed);
125
+ if(debug) console.log("Auto-rotate", orbit.autoRotate, "speed:", orbit.autoRotateSpeed);
126
126
  const autoFit = context.domElement.getAttribute("auto-fit");
127
127
  orbit.autoFit = autoFit !== "0" && autoFit?.toLowerCase() != "false";
128
128
  orbit.autoTarget = true;
@@ -14,7 +14,7 @@ import { Rigidbody } from "./RigidBody.js";
14
14
  const debug = getParam("debugcharactercontroller");
15
15
 
16
16
  /**
17
- * @category Camera Controls
17
+ * @category Camera
18
18
  * @group Components
19
19
  */
20
20
  export class CharacterController extends Behaviour {
@@ -107,7 +107,7 @@ export class CharacterController extends Behaviour {
107
107
  }
108
108
 
109
109
  /**
110
- * @category Camera Controls
110
+ * @category Camera
111
111
  * @category Interactivity
112
112
  * @group Components
113
113
  */
@@ -63,7 +63,7 @@ declare module 'three/examples/jsm/controls/OrbitControls.js' {
63
63
  /** The OrbitControls component is used to control a camera using the [OrbitControls from three.js](https://threejs.org/docs/#examples/en/controls/OrbitControls) library.
64
64
  * The three OrbitControls object can be accessed via the `controls` property.
65
65
  * The object being controlled by the OrbitControls (usually the camera) can be accessed via the `controllerObject` property.
66
- * @category Camera Controls
66
+ * @category Camera
67
67
  * @group Components
68
68
  */
69
69
  export class OrbitControls extends Behaviour implements ICameraController {
@@ -993,11 +993,50 @@ export class OrbitControls extends Behaviour implements ICameraController {
993
993
 
994
994
  // Adapted from https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
995
995
  // Slower but better implementation that takes bones and exact vertex positions into account: https://github.com/google/model-viewer/blob/04e900c5027de8c5306fe1fe9627707f42811b05/packages/model-viewer/src/three-components/ModelScene.ts#L321
996
+
996
997
  /**
997
998
  * Fits the camera to show the objects provided (defaults to the scene if no objects are passed in)
999
+ * @param options The options for fitting the camera. Use to provide objects to fit to, fit direction and size and other settings.
998
1000
  */
999
- fitCamera(options?: OrbitFitCameraOptions): void {
1001
+ fitCamera(options?: OrbitFitCameraOptions);
1002
+ /** @deprecated Use fitCamera(options) */
1003
+ fitCamera(objects?: Object3D | Array<Object3D>, options?: Omit<OrbitFitCameraOptions, "objects">);
1004
+ fitCamera(objectsOrOptions?: Object3D | Array<Object3D> | OrbitFitCameraOptions, options?: OrbitFitCameraOptions): void {
1005
+
1006
+
1007
+ let objects: Object3D | Array<Object3D> | undefined = undefined;
1008
+ // If the user passed in an array as first argument
1009
+ if (Array.isArray(objectsOrOptions)) {
1010
+ objects = objectsOrOptions;
1011
+ }
1012
+ // If the user passed in an object as first argument
1013
+ else if (objectsOrOptions && "type" in objectsOrOptions) {
1014
+ objects = objectsOrOptions;
1015
+ }
1016
+ // If the user passed in an object as first argument and options as second argument
1017
+ else if (objectsOrOptions && typeof objectsOrOptions === "object") {
1018
+ if (!(objectsOrOptions instanceof Object3D) && !Array.isArray(objectsOrOptions)) {
1019
+ options = objectsOrOptions;
1020
+ objects = options.objects;
1021
+ }
1022
+ }
1023
+ // Ensure objects are setup correctly
1024
+ if (objects && !Array.isArray(objects)) {
1025
+ objects = [objects];
1026
+ }
1027
+ if (!Array.isArray(objects) || objects && objects.length <= 0) {
1028
+ objects = this.context.scene.children;
1029
+ }
1030
+
1031
+ // Make sure there's anything to fit to
1032
+ if (!Array.isArray(objects) || objects.length <= 0) {
1033
+ console.warn("No objects to fit camera to...");
1034
+ return;
1035
+ }
1036
+
1037
+
1000
1038
  const res = fitCamera({
1039
+ objects: [...objects],
1001
1040
  ...options,
1002
1041
  autoApply: false,
1003
1042
  context: this.context,
@@ -358,8 +358,11 @@ export class Renderer extends Behaviour implements IRenderer {
358
358
  //@ts-ignore
359
359
  get sharedMaterials(): SharedMaterialArray {
360
360
 
361
+ // @ts-ignore (original materials will be set during deserialization)
362
+ if(this._originalMaterials === undefined) return null;
363
+
361
364
  // @ts-ignore during deserialization code might access this property *before* the setter and then create an empty array
362
- if (!this.__didAwake) return null;
365
+ if (this.__isDeserializing === true) return null;
363
366
 
364
367
  if (!this._sharedMaterials || !this._sharedMaterials.is(this)) {
365
368
  if (!this._originalMaterials) this._originalMaterials = [];
@@ -725,6 +728,8 @@ export class Renderer extends Behaviour implements IRenderer {
725
728
  // If the material has a envMap and is NOT using a reflection probe we set the envMap to the scene environment
726
729
  if (mat && "envMap" in mat && "envMapIntensity" in mat && !ReflectionProbe.isUsingReflectionProbe(mat)) {
727
730
  mat.envMap = this.context.scene.environment;
731
+ mat.envMapIntensity = this.context.scene.environmentIntensity;
732
+ mat.envMapRotation = this.context.scene.environmentRotation;
728
733
  }
729
734
  }
730
735
  }
@@ -8,7 +8,7 @@ import { syncField } from "../engine/engine_networking_auto.js";
8
8
  import { loadPMREM } from "../engine/engine_pmrem.js";
9
9
  import { serializable } from "../engine/engine_serialization_decorator.js";
10
10
  import { type IContext } from "../engine/engine_types.js";
11
- import { addAttributeChangeCallback, getParam, PromiseAllWithErrors, removeAttributeChangeCallback } from "../engine/engine_utils.js";
11
+ import { addAttributeChangeCallback, getParam, PromiseAllWithErrors, removeAttributeChangeCallback, toSourceId } from "../engine/engine_utils.js";
12
12
  import { registerObservableAttribute } from "../engine/webcomponents/needle-engine.extras.js";
13
13
  import { Camera, ClearFlags } from "./Camera.js";
14
14
  import { Behaviour, GameObject } from "./Component.js";
@@ -27,15 +27,34 @@ function createRemoteSkyboxComponent(context: IContext, url: string, skybox: boo
27
27
  }
28
28
 
29
29
  const remote = new RemoteSkybox();
30
+ remote.sourceId = toSourceId(url);
30
31
  remote.allowDrop = false;
31
32
  remote.allowNetworking = false;
32
33
  remote.background = skybox;
33
34
  remote.environment = environment;
34
35
  GameObject.addComponent(context.scene, remote);
35
36
  const urlChanged = newValue => {
36
- if (typeof newValue !== "string") return;
37
- if (debug) console.log(attribute, "CHANGED TO", newValue)
38
- remote.setSkybox(newValue);
37
+ if (debug) console.log(attribute, "CHANGED TO", newValue);
38
+ if (newValue) {
39
+ if (typeof newValue !== "string") {
40
+ console.warn("Invalid attribute value for " + attribute);
41
+ return;
42
+ }
43
+ remote.setSkybox(newValue);
44
+ }
45
+ else {
46
+ if (remote.sourceId) {
47
+ if (environment) {
48
+ if (!context.sceneLighting.internalEnableReflection(remote.sourceId)) {
49
+ context.scene.environment = null;
50
+ }
51
+ }
52
+ if (skybox) {
53
+ const skybox = context.lightmaps.tryGetSkybox(remote.sourceId);
54
+ context.scene.background = skybox;
55
+ }
56
+ }
57
+ }
39
58
  };
40
59
  addAttributeChangeCallback(context.domElement, attribute, urlChanged);
41
60
  remote.addEventListener("destroy", () => {
@@ -50,7 +69,7 @@ ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, (args) => {
50
69
  const context = args.context;
51
70
  const backgroundImage = context.domElement.getAttribute("background-image");
52
71
  const environmentImage = context.domElement.getAttribute("environment-image");
53
-
72
+
54
73
  if (backgroundImage) {
55
74
  if (debug) console.log("Creating RemoteSkybox to load background " + backgroundImage);
56
75
  // if the user is loading a GLB without a camera then the CameraUtils (which creates the default camera)
@@ -246,8 +265,8 @@ export class RemoteSkybox extends Behaviour {
246
265
  envMap.needsUpdate = true;
247
266
  }
248
267
 
249
- if(this.destroyed) return;
250
- if(!this.context) {
268
+ if (this.destroyed) return;
269
+ if (!this.context) {
251
270
  console.warn("RemoteSkybox: Context is not available - can not apply skybox.");
252
271
  return;
253
272
  }
@@ -208,6 +208,7 @@ export { ClickThrough } from "../web/Clickthrough.js";
208
208
  export { CursorFollow } from "../web/CursorFollow.js";
209
209
  export { HoverAnimation } from "../web/HoverAnimation.js";
210
210
  export { ScrollFollow } from "../web/ScrollFollow.js";
211
+ export { ViewBox } from "../web/ViewBox.js";
211
212
  export { Avatar } from "../webxr/Avatar.js";
212
213
  export { XRControllerFollow } from "../webxr/controllers/XRControllerFollow.js";
213
214
  export { XRControllerModel } from "../webxr/controllers/XRControllerModel.js";
@@ -4,6 +4,7 @@ import { Behaviour } from "../../engine-components/Component.js";
4
4
 
5
5
  const debug = getParam("logstats");
6
6
 
7
+ /** @internal */
7
8
  export class LogStats extends Behaviour {
8
9
 
9
10
  onEnable(): void {
@@ -66,10 +66,19 @@ export class PlayableDirector extends Behaviour {
66
66
  this.createTrackFunctions[type] = fn;
67
67
  }
68
68
 
69
+ /**
70
+ * The timeline asset that is played by this director.
71
+ */
69
72
  playableAsset?: Models.TimelineAssetModel;
73
+
70
74
  /** Set to true to start playing the timeline when the scene starts */
71
75
  @serializable()
72
76
  playOnAwake?: boolean;
77
+
78
+ /**
79
+ * Determines how the timeline behaves when it reaches the end of its duration.
80
+ * @default DirectorWrapMode.Loop
81
+ */
73
82
  @serializable()
74
83
  extrapolationMode: DirectorWrapMode = DirectorWrapMode.Loop;
75
84
 
@@ -106,7 +115,7 @@ export class PlayableDirector extends Behaviour {
106
115
  /** @internal */
107
116
  awake(): void {
108
117
  if (debug)
109
- console.log(this, this.playableAsset?.tracks);
118
+ console.log(this, this.playableAsset);
110
119
 
111
120
  this.rebuildGraph();
112
121
 
@@ -1,11 +1,16 @@
1
1
  import { AnimationClip, Object3D, Quaternion, Vector3 } from "three";
2
- import { Behavior } from "three-mesh-ui";
3
2
 
3
+ /**
4
+ * @category Animation and Sequencing
5
+ */
4
6
  export declare type TimelineAssetModel = {
5
7
  name: string;
6
8
  tracks: TrackModel[];
7
9
  }
8
10
 
11
+ /**
12
+ * @category Animation and Sequencing
13
+ */
9
14
  export enum TrackType {
10
15
  Activation = "ActivationTrack",
11
16
  Animation = "AnimationTrack",
@@ -15,6 +20,9 @@ export enum TrackType {
15
20
  Signal = "SignalTrack",
16
21
  }
17
22
 
23
+ /**
24
+ * @category Animation and Sequencing
25
+ */
18
26
  export enum ClipExtrapolation {
19
27
  None = 0,
20
28
  Hold = 1,
@@ -23,6 +31,9 @@ export enum ClipExtrapolation {
23
31
  Continue = 4
24
32
  };
25
33
 
34
+ /**
35
+ * @category Animation and Sequencing
36
+ */
26
37
  export declare type TrackModel = {
27
38
  name: string;
28
39
  type: TrackType;
@@ -37,11 +48,17 @@ export declare type TrackModel = {
37
48
  declare type Vec3 = { x: number, y: number, z: number };
38
49
  declare type Quat = { x: number, y: number, z: number, w: number };
39
50
 
51
+ /**
52
+ * @category Animation and Sequencing
53
+ */
40
54
  export declare type TrackOffset = {
41
55
  position: Vec3 | Vector3;
42
56
  rotation: Quat | Quaternion;
43
57
  }
44
58
 
59
+ /**
60
+ * @category Animation and Sequencing
61
+ */
45
62
  export declare type ClipModel = {
46
63
  start: number;
47
64
  end: number;
@@ -56,6 +73,9 @@ export declare type ClipModel = {
56
73
  reversed?: boolean;
57
74
  }
58
75
 
76
+ /**
77
+ * @category Animation and Sequencing
78
+ */
59
79
  export declare type AnimationClipModel = {
60
80
  clip: string | number | AnimationClip;
61
81
  loop: boolean;
@@ -65,12 +85,18 @@ export declare type AnimationClipModel = {
65
85
  rotation?: Quat | Quaternion;
66
86
  }
67
87
 
88
+ /**
89
+ * @category Animation and Sequencing
90
+ */
68
91
  export declare type AudioClipModel = {
69
92
  clip: string;
70
93
  loop: boolean;
71
94
  volume: number;
72
95
  }
73
96
 
97
+ /**
98
+ * @category Animation and Sequencing
99
+ */
74
100
  export declare type ControlClipModel = {
75
101
  sourceObject: string | Object3D;
76
102
  controlActivation: boolean;
@@ -81,15 +107,31 @@ export enum MarkerType {
81
107
  Signal = "SignalEmitter",
82
108
  }
83
109
 
84
- export declare class MarkerModel {
110
+ /**
111
+ * @category Animation and Sequencing
112
+ */export declare class MarkerModel {
85
113
  type: MarkerType;
86
114
  time: number;
87
115
  }
88
116
 
117
+ /**
118
+ * @category Animation and Sequencing
119
+ */
89
120
  export declare class SignalMarkerModel extends MarkerModel {
90
121
  retroActive: boolean;
91
122
  emitOnce: boolean;
92
123
  asset: string;
93
124
  }
94
125
 
95
- export type ScrollMarkerModel = MarkerModel & { selector: string };
126
+ /**
127
+ * Marker with a name, used for scroll-driven timelines. It is used together with elements in your HTML to define what time in the timeline should be active when the element is in the scroll view.
128
+ *
129
+ * @example Mark html elements to define scroll positions
130
+ * ```html
131
+ * <div data-timeline-marker>...</div>
132
+ * ```
133
+ *
134
+ * @link [Example Project using ScrollMarker](https://scrollytelling-bike-z23hmxb2gnu5a.needle.run/)
135
+ * @category Animation and Sequencing
136
+ */
137
+ export type ScrollMarkerModel = MarkerModel & { name: string };
@@ -175,8 +175,10 @@ export class AnimationTrackHandler extends TrackHandler {
175
175
  // which means we want to notify the object that it's not animated anymore
176
176
  // and the animator can then take over
177
177
  onStateChanged() {
178
- if (this._animator)
179
- setObjectAnimated(this._animator.gameObject, this, this.director.isPlaying);
178
+ if (this._animator) {
179
+ // We can not check the *isPlaying* state here because the timeline might be paused and evaluated by e.g. ScrollFollow
180
+ setObjectAnimated(this._animator.gameObject, this, this.director.enabled && this.director.weight > 0);
181
+ }
180
182
  }
181
183
 
182
184
  createHooks(clipModel: Models.AnimationClipModel, clip) {
@@ -185,21 +187,22 @@ export class AnimationTrackHandler extends TrackHandler {
185
187
  return;
186
188
  }
187
189
  // we only want to hook into the binding of the root object
188
- // TODO: test with a clip with multiple roots
189
- const parts = clip.tracks[0].name.split(".");
190
- const rootName = parts[parts.length - 2];
191
- const positionTrackName = rootName + ".position";
192
- const rotationTrackName = rootName + ".quaternion";
193
190
  let foundPositionTrack: boolean = false;
194
191
  let foundRotationTrack: boolean = false;
195
- for (const t of clip.tracks) {
196
- if (t.name.endsWith(positionTrackName)) {
197
- foundPositionTrack = true;
198
- this.createPositionInterpolant(clip, clipModel, t);
199
- }
200
- else if (t.name.endsWith(rotationTrackName)) {
201
- foundRotationTrack = true;
202
- this.createRotationInterpolant(clip, clipModel, t);
192
+ const parts = clip.tracks.find(t => t.name.includes(".position") || t.name.includes(".quaternion"))?.name.split(".");
193
+ if (parts) {
194
+ const rootName = parts[parts.length - 2];
195
+ const positionTrackName = rootName + ".position";
196
+ const rotationTrackName = rootName + ".quaternion";
197
+ for (const t of clip.tracks) {
198
+ if (!foundPositionTrack && t.name.endsWith(positionTrackName)) {
199
+ foundPositionTrack = true;
200
+ this.createPositionInterpolant(clip, clipModel, t);
201
+ }
202
+ else if (!foundRotationTrack && t.name.endsWith(rotationTrackName)) {
203
+ foundRotationTrack = true;
204
+ this.createRotationInterpolant(clip, clipModel, t);
205
+ }
203
206
  }
204
207
  }
205
208
 
@@ -224,16 +227,14 @@ export class AnimationTrackHandler extends TrackHandler {
224
227
  const trackName = baseName + ".position";
225
228
  if (debug) console.warn("Create position track", objName, targetObj);
226
229
  // apply initial local position so it doesnt get flipped or otherwise changed
227
- const pos = targetObj.position;
228
- const track = new VectorKeyframeTrack(trackName, [0, clip.duration], [pos.x, pos.y, pos.z, pos.x, pos.y, pos.z]);
230
+ const track = new VectorKeyframeTrack(trackName, [0, clip.duration], [0, 0, 0, 0, 0, 0]);
229
231
  clip.tracks.push(track);
230
232
  this.createPositionInterpolant(clip, clipModel, track);
231
233
  }
232
234
  else if (!foundRotationTrack) {
233
235
  const trackName = clip.tracks[0].name.substring(0, indexOfProperty) + ".quaternion";
234
236
  if (debug) console.warn("Create quaternion track", objName, targetObj);
235
- const rot = targetObj.quaternion;
236
- const track = new QuaternionKeyframeTrack(trackName, [0, clip.duration], [rot.x, rot.y, rot.z, rot.w, rot.x, rot.y, rot.z, rot.w]);
237
+ const track = new QuaternionKeyframeTrack(trackName, [0, clip.duration], [0, 0, 0, 1, 0, 0, 0, 1]);
237
238
  clip.tracks.push(track);
238
239
  this.createRotationInterpolant(clip, clipModel, track);
239
240
  }
@@ -832,23 +833,27 @@ export class AudioTrackHandler extends TrackHandler {
832
833
 
833
834
  export class MarkerTrackHandler extends TrackHandler {
834
835
  models: Array<Models.MarkerModel & Record<string, any>> = [];
835
- isDirty = true;
836
+ needsSorting = true;
836
837
 
837
838
  *foreachMarker<T>(type: string | null = null) {
839
+ if(this.needsSorting) this.sort();
838
840
  for (const model of this.models) {
839
841
  if (model && model.type === type) yield model as T;
840
842
  }
841
843
  }
842
844
 
843
845
  onEnable() {
844
- this.isDirty = true;
846
+ this.needsSorting = true;
845
847
  }
846
848
 
847
849
  evaluate(_time: number) {
848
- if (this.isDirty) {
849
- this.isDirty = false;
850
- this.models.sort((a, b) => a.time - b.time);
851
- }
850
+ if (this.needsSorting) this.sort();
851
+ }
852
+
853
+ private sort() {
854
+ this.needsSorting = false;
855
+ this.models.sort((a, b) => a.time - b.time);
856
+
852
857
  }
853
858
  }
854
859
 
@@ -1,5 +1,6 @@
1
1
  import { Matrix4, Object3D, Quaternion, Vector3 } from "three";
2
2
 
3
+ import { isDevEnvironment } from "../../engine/debug/index.js";
3
4
  import { serializable } from "../../engine/engine_serialization.js";
4
5
  import { lookAtObject } from "../../engine/engine_three_utils.js";
5
6
  import { type UsdzBehaviour } from "../../engine-components/export/usdz/extensions/behavior/Behaviour.js";
@@ -42,7 +43,10 @@ export class LookAt extends Behaviour implements UsdzBehaviour {
42
43
  /** @internal */
43
44
  onBeforeRender(): void {
44
45
  let target: Object3D | null | undefined = this.target;
45
- if (!target) target = this.context.mainCamera;
46
+ if (!target) {
47
+ target = this.context.mainCamera;
48
+ if (isDevEnvironment()) console.warn(`[LookAt] No target set on ${this.name}, using main camera as target.`);
49
+ }
46
50
  if (!target) return;
47
51
 
48
52
  let copyTargetRotation = this.copyTargetRotation;