@needle-tools/engine 4.10.0-next.f0ec242 → 4.10.0-next.f5ce0ae

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 (122) hide show
  1. package/components.needle.json +1 -1
  2. package/dist/{needle-engine.bundle-BC-0Ex9m.js → needle-engine.bundle-DSzlnhCs.js} +7388 -7113
  3. package/dist/{needle-engine.bundle-dgNq9Vsa.umd.cjs → needle-engine.bundle-VqrrECGF.umd.cjs} +153 -140
  4. package/dist/needle-engine.bundle-pI8o_eru.min.js +1652 -0
  5. package/dist/needle-engine.d.ts +15 -15
  6. package/dist/needle-engine.js +259 -257
  7. package/dist/needle-engine.min.js +1 -1
  8. package/dist/needle-engine.umd.cjs +1 -1
  9. package/dist/vendor-CPuBPspY.umd.cjs +1121 -0
  10. package/dist/vendor-DPCU8cUF.min.js +1121 -0
  11. package/dist/vendor-MBoqSyFm.js +16240 -0
  12. package/lib/engine/codegen/register_types.js +2 -0
  13. package/lib/engine/codegen/register_types.js.map +1 -1
  14. package/lib/engine/engine_camera.d.ts +7 -1
  15. package/lib/engine/engine_camera.js +46 -6
  16. package/lib/engine/engine_camera.js.map +1 -1
  17. package/lib/engine/engine_context.d.ts +6 -0
  18. package/lib/engine/engine_context.js +48 -9
  19. package/lib/engine/engine_context.js.map +1 -1
  20. package/lib/engine/engine_gizmos.d.ts +11 -10
  21. package/lib/engine/engine_gizmos.js +24 -10
  22. package/lib/engine/engine_gizmos.js.map +1 -1
  23. package/lib/engine/engine_license.js +1 -1
  24. package/lib/engine/engine_license.js.map +1 -1
  25. package/lib/engine/engine_lightdata.d.ts +3 -3
  26. package/lib/engine/engine_lightdata.js +10 -10
  27. package/lib/engine/engine_lightdata.js.map +1 -1
  28. package/lib/engine/engine_physics_rapier.js +4 -0
  29. package/lib/engine/engine_physics_rapier.js.map +1 -1
  30. package/lib/engine/engine_scenelighting.d.ts +1 -1
  31. package/lib/engine/engine_scenelighting.js +4 -5
  32. package/lib/engine/engine_scenelighting.js.map +1 -1
  33. package/lib/engine/engine_utils.d.ts +3 -1
  34. package/lib/engine/engine_utils.js +11 -0
  35. package/lib/engine/engine_utils.js.map +1 -1
  36. package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
  37. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  38. package/lib/engine/extensions/extension_utils.js +1 -1
  39. package/lib/engine/extensions/extension_utils.js.map +1 -1
  40. package/lib/engine/webcomponents/logo-element.d.ts +1 -1
  41. package/lib/engine/webcomponents/logo-element.js +29 -5
  42. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  43. package/lib/engine/webcomponents/needle menu/needle-menu.js +4 -3
  44. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  45. package/lib/engine/webcomponents/needle-engine.js +22 -0
  46. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  47. package/lib/engine/webcomponents/needle-engine.loading.d.ts +0 -1
  48. package/lib/engine/webcomponents/needle-engine.loading.js +3 -36
  49. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  50. package/lib/engine/xr/NeedleXRController.d.ts +3 -3
  51. package/lib/engine/xr/NeedleXRController.js +28 -0
  52. package/lib/engine/xr/NeedleXRController.js.map +1 -1
  53. package/lib/engine-components/CameraUtils.js +2 -1
  54. package/lib/engine-components/CameraUtils.js.map +1 -1
  55. package/lib/engine-components/Renderer.js +6 -1
  56. package/lib/engine-components/Renderer.js.map +1 -1
  57. package/lib/engine-components/Skybox.js +22 -4
  58. package/lib/engine-components/Skybox.js.map +1 -1
  59. package/lib/engine-components/codegen/components.d.ts +1 -0
  60. package/lib/engine-components/codegen/components.js +1 -0
  61. package/lib/engine-components/codegen/components.js.map +1 -1
  62. package/lib/engine-components/debug/LogStats.d.ts +1 -0
  63. package/lib/engine-components/debug/LogStats.js +1 -0
  64. package/lib/engine-components/debug/LogStats.js.map +1 -1
  65. package/lib/engine-components/timeline/PlayableDirector.d.ts +7 -0
  66. package/lib/engine-components/timeline/PlayableDirector.js +8 -1
  67. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  68. package/lib/engine-components/timeline/TimelineModels.d.ts +9 -1
  69. package/lib/engine-components/timeline/TimelineTracks.d.ts +2 -1
  70. package/lib/engine-components/timeline/TimelineTracks.js +30 -25
  71. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  72. package/lib/engine-components/utils/LookAt.js +5 -1
  73. package/lib/engine-components/utils/LookAt.js.map +1 -1
  74. package/lib/engine-components/web/Clickthrough.js +10 -2
  75. package/lib/engine-components/web/Clickthrough.js.map +1 -1
  76. package/lib/engine-components/web/ScrollFollow.d.ts +23 -0
  77. package/lib/engine-components/web/ScrollFollow.js +168 -42
  78. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  79. package/lib/engine-components/web/ViewBox.d.ts +27 -0
  80. package/lib/engine-components/web/ViewBox.js +242 -0
  81. package/lib/engine-components/web/ViewBox.js.map +1 -0
  82. package/lib/engine-components/web/index.d.ts +1 -0
  83. package/lib/engine-components/web/index.js +1 -0
  84. package/lib/engine-components/web/index.js.map +1 -1
  85. package/lib/engine-components-experimental/Presentation.d.ts +1 -0
  86. package/lib/engine-components-experimental/Presentation.js +1 -0
  87. package/lib/engine-components-experimental/Presentation.js.map +1 -1
  88. package/package.json +2 -1
  89. package/src/engine/codegen/register_types.ts +2 -0
  90. package/src/engine/engine_camera.ts +61 -9
  91. package/src/engine/engine_context.ts +50 -10
  92. package/src/engine/engine_gizmos.ts +37 -23
  93. package/src/engine/engine_license.ts +1 -1
  94. package/src/engine/engine_lightdata.ts +11 -11
  95. package/src/engine/engine_physics_rapier.ts +3 -0
  96. package/src/engine/engine_scenelighting.ts +5 -6
  97. package/src/engine/engine_utils.ts +12 -0
  98. package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
  99. package/src/engine/extensions/extension_utils.ts +1 -1
  100. package/src/engine/webcomponents/logo-element.ts +29 -4
  101. package/src/engine/webcomponents/needle menu/needle-menu.ts +4 -3
  102. package/src/engine/webcomponents/needle-engine.loading.ts +32 -32
  103. package/src/engine/webcomponents/needle-engine.ts +33 -6
  104. package/src/engine/xr/NeedleXRController.ts +36 -4
  105. package/src/engine-components/CameraUtils.ts +1 -1
  106. package/src/engine-components/Renderer.ts +6 -1
  107. package/src/engine-components/Skybox.ts +26 -7
  108. package/src/engine-components/codegen/components.ts +1 -0
  109. package/src/engine-components/debug/LogStats.ts +1 -0
  110. package/src/engine-components/timeline/PlayableDirector.ts +10 -1
  111. package/src/engine-components/timeline/TimelineModels.ts +9 -1
  112. package/src/engine-components/timeline/TimelineTracks.ts +30 -25
  113. package/src/engine-components/utils/LookAt.ts +5 -1
  114. package/src/engine-components/web/Clickthrough.ts +11 -2
  115. package/src/engine-components/web/ScrollFollow.ts +204 -51
  116. package/src/engine-components/web/ViewBox.ts +262 -0
  117. package/src/engine-components/web/index.ts +2 -1
  118. package/src/engine-components-experimental/Presentation.ts +1 -0
  119. package/dist/needle-engine.bundle-BSh7dSEx.min.js +0 -1639
  120. package/dist/vendor-D0Yvltn9.umd.cjs +0 -1121
  121. package/dist/vendor-DU8tJyl_.js +0 -14366
  122. package/dist/vendor-JyrX4DVM.min.js +0 -1121
@@ -32,10 +32,22 @@ export class NeedleLogoElement extends HTMLElement {
32
32
  cursor: pointer;
33
33
  }
34
34
  img {
35
- width: 95px;
36
35
  height: 100%;
37
36
  align-self: end;
38
37
  margin-left: 0.6rem;
38
+ transition: transform 0.2s;
39
+ }
40
+ img.with-text {
41
+ width: 11.5ch;
42
+ &:hover {
43
+ transform: scale(1.02);
44
+ }
45
+ }
46
+ img.compact {
47
+ width: 1.7em;
48
+ &:hover {
49
+ transform: scale(1.1);
50
+ }
39
51
  }
40
52
  span {
41
53
  font-size: 1rem;
@@ -43,12 +55,14 @@ export class NeedleLogoElement extends HTMLElement {
43
55
  }
44
56
  </style>
45
57
  <div class="wrapper">
46
- <img class="logo" src=${needleLogoSVG} />
58
+ <img class="logo with-text" src=${needleLogoSVG} />
47
59
  </div>
48
60
  `;
49
61
  this._root.appendChild(template.content.cloneNode(true));
50
62
  this.wrapper = this._root.querySelector(".wrapper") as HTMLDivElement;
51
63
  this._root.appendChild(this.wrapper);
64
+ this.logoElement = this._root.querySelector("img.logo") as HTMLImageElement;
65
+
52
66
  // this.wrapper.classList.add("wrapper");
53
67
 
54
68
  // this.wrapper.appendChild(this.logoElement);
@@ -67,13 +81,24 @@ export class NeedleLogoElement extends HTMLElement {
67
81
 
68
82
  private readonly _root: ShadowRoot;
69
83
  private readonly wrapper: HTMLDivElement;
70
- private readonly logoElement: HTMLImageElement = document.createElement("img");
71
- private readonly textElement: HTMLSpanElement = document.createElement("span");
84
+ private readonly logoElement: HTMLImageElement;
72
85
 
73
86
  setLogoVisible(val: boolean) {
74
87
  this.logoElement.style.display = val ? "block" : "none";
75
88
  }
76
89
 
90
+ setType(type: "full" | "compact") {
91
+ if (type === "full") {
92
+ this.logoElement.src = needleLogoSVG;
93
+ this.logoElement.classList.remove("with-text");
94
+ this.logoElement.classList.remove("compact");
95
+ } else {
96
+ this.logoElement.src = needleLogoOnlySVG;
97
+ this.logoElement.classList.add("with-text");
98
+ this.logoElement.classList.add("compact");
99
+ }
100
+ }
101
+
77
102
  }
78
103
  if (!customElements.get(elementName))
79
104
  customElements.define(elementName, NeedleLogoElement);
@@ -459,7 +459,7 @@ export class NeedleMenuElement extends HTMLElement {
459
459
 
460
460
  .logo {
461
461
  cursor: pointer;
462
- padding-left: 0.6rem;
462
+ padding-left: 0.0rem;
463
463
  padding-bottom: .02rem;
464
464
  margin-right: 0.5rem;
465
465
  }
@@ -664,8 +664,8 @@ export class NeedleMenuElement extends HTMLElement {
664
664
  <slot name="end"></slot>
665
665
  </div>
666
666
  </div>
667
- <div style="user-select:none" class="logo">
668
- <span class="madewith notranslate">powered by</span>
667
+ <div style="user-select:none;" class="logo">
668
+ <span class="madewith notranslate" style="display:none;">powered by</span>
669
669
  </div>
670
670
  </div>
671
671
  <button class="compact-menu-button">
@@ -698,6 +698,7 @@ export class NeedleMenuElement extends HTMLElement {
698
698
  this.wrapper.classList.add("wrapper");
699
699
 
700
700
  const logo = NeedleLogoElement.create();
701
+ logo.setType("compact");
701
702
  logo.style.minHeight = "1rem";
702
703
  this.logoContainer.append(logo);
703
704
  this.logoContainer.addEventListener("click", () => {
@@ -374,40 +374,40 @@ export class EngineLoadingView implements ILoadingViewHandler {
374
374
  // }
375
375
  // }
376
376
 
377
- this.handleRuntimeLicense(this._loadingElement);
377
+ // this.handleRuntimeLicense(this._loadingElement);
378
378
 
379
379
  return this._loadingElement;
380
380
  }
381
381
 
382
- private async handleRuntimeLicense(loadingElement: HTMLElement) {
383
- // First check if we have a commercial license
384
- let commercialLicense = hasCommercialLicense();
385
- // if it's the case then we don't need to perform a runtime check
386
- if (commercialLicense) return;
387
-
388
- // If we don't have a commercial license, then we need to display our message
389
- if (debugLicense) console.log("Loading UI has commercial license?", commercialLicense);
390
- const nonCommercialContainer = document.createElement("div");
391
- nonCommercialContainer.style.paddingTop = ".6em";
392
- nonCommercialContainer.style.fontSize = ".8em";
393
- nonCommercialContainer.style.textTransform = "uppercase";
394
- nonCommercialContainer.innerText = "NEEDLE ENGINE NON COMMERCIAL VERSION\nCLICK HERE TO GET A LICENSE";
395
- nonCommercialContainer.style.cursor = "pointer";
396
- nonCommercialContainer.style.userSelect = "none";
397
- nonCommercialContainer.style.textAlign = "center";
398
- nonCommercialContainer.style.pointerEvents = "all";
399
- nonCommercialContainer.addEventListener("click", () => window.open("https://needle.tools/pricing", "_self"));
400
- nonCommercialContainer.style.opacity = "0";
401
- loadingElement.appendChild(nonCommercialContainer);
402
-
403
- // Use the runtime license check
404
- if (!isDevEnvironment() && runtimeLicenseCheckPromise) {
405
- if (debugLicense) console.log("Waiting for runtime license check");
406
- await runtimeLicenseCheckPromise;
407
- commercialLicense = hasCommercialLicense();
408
- }
409
- if (commercialLicense) return;
410
- nonCommercialContainer.style.transition = "opacity .5s ease-in-out";
411
- nonCommercialContainer.style.opacity = "1";
412
- }
382
+ // private async handleRuntimeLicense(loadingElement: HTMLElement) {
383
+ // // First check if we have a commercial license
384
+ // let commercialLicense = hasCommercialLicense();
385
+ // // if it's the case then we don't need to perform a runtime check
386
+ // if (commercialLicense) return;
387
+
388
+ // // If we don't have a commercial license, then we need to display our message
389
+ // if (debugLicense) console.log("Loading UI has commercial license?", commercialLicense);
390
+ // const nonCommercialContainer = document.createElement("div");
391
+ // nonCommercialContainer.style.paddingTop = ".6em";
392
+ // nonCommercialContainer.style.fontSize = ".8em";
393
+ // nonCommercialContainer.style.textTransform = "uppercase";
394
+ // nonCommercialContainer.innerText = "NEEDLE ENGINE NON COMMERCIAL VERSION\nCLICK HERE TO GET A LICENSE";
395
+ // nonCommercialContainer.style.cursor = "pointer";
396
+ // nonCommercialContainer.style.userSelect = "none";
397
+ // nonCommercialContainer.style.textAlign = "center";
398
+ // nonCommercialContainer.style.pointerEvents = "all";
399
+ // nonCommercialContainer.addEventListener("click", () => window.open("https://needle.tools/pricing", "_self"));
400
+ // nonCommercialContainer.style.opacity = "0";
401
+ // loadingElement.appendChild(nonCommercialContainer);
402
+
403
+ // // Use the runtime license check
404
+ // if (!isDevEnvironment() && runtimeLicenseCheckPromise) {
405
+ // if (debugLicense) console.log("Waiting for runtime license check");
406
+ // await runtimeLicenseCheckPromise;
407
+ // commercialLicense = hasCommercialLicense();
408
+ // }
409
+ // if (commercialLicense) return;
410
+ // nonCommercialContainer.style.transition = "opacity .5s ease-in-out";
411
+ // nonCommercialContainer.style.opacity = "1";
412
+ // }
413
413
  }
@@ -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;
@@ -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;
@@ -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
 
@@ -92,4 +92,12 @@ export declare class SignalMarkerModel extends MarkerModel {
92
92
  asset: string;
93
93
  }
94
94
 
95
- export type ScrollMarkerModel = MarkerModel & { selector: string };
95
+ /**
96
+ * 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.
97
+ *
98
+ * @example Mark html elements to define scroll positions
99
+ * ```html
100
+ * <div data-timeline-marker>...</div>
101
+ * ```
102
+ */
103
+ 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;
@@ -1,12 +1,21 @@
1
1
  import { NEPointerEvent } from "../../engine/engine_input.js";
2
2
  import { onStart } from "../../engine/engine_lifecycle_api.js";
3
+ import { addAttributeChangeCallback } from "../../engine/engine_utils.js";
3
4
  import { Behaviour } from "../Component.js";
4
5
 
5
6
  // Automatically add ClickThrough component if "clickthrough" attribute is present on the needle-engine element
6
7
  onStart(ctx => {
7
8
  const attribute = ctx.domElement.getAttribute("clickthrough");
8
- if (attribute !== null && attribute !== "0" && attribute !== "false") {
9
- ctx.scene.addComponent(ClickThrough);
9
+ if (clickthroughEnabled(attribute)) {
10
+ const comp = ctx.scene.addComponent(ClickThrough);
11
+ addAttributeChangeCallback(ctx.domElement, "clickthrough", () => {
12
+ const attribute = ctx.domElement.getAttribute("clickthrough");
13
+ comp.enabled = clickthroughEnabled(attribute);
14
+ });
15
+ }
16
+
17
+ function clickthroughEnabled(val: string | null) {
18
+ return val !== null && val !== "0" && val !== "false";
10
19
  }
11
20
  });
12
21