@needle-tools/engine 4.10.0-beta.3 → 4.10.0-beta.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 (51) hide show
  1. package/components.needle.json +1 -1
  2. package/dist/{needle-engine.bundle-CLzMgxkO.min.js → needle-engine.bundle-BGVvfSfQ.min.js} +132 -130
  3. package/dist/{needle-engine.bundle-Ddybtee9.js → needle-engine.bundle-DcvMQ1Nq.js} +6115 -6083
  4. package/dist/{needle-engine.bundle-Ckr5KE6m.umd.cjs → needle-engine.bundle-DzVytSSo.umd.cjs} +133 -131
  5. package/dist/needle-engine.js +332 -331
  6. package/dist/needle-engine.min.js +1 -1
  7. package/dist/needle-engine.umd.cjs +1 -1
  8. package/lib/engine/codegen/register_types.js +1 -1
  9. package/lib/engine/codegen/register_types.js.map +1 -1
  10. package/lib/engine/engine_camera.js +5 -5
  11. package/lib/engine/engine_camera.js.map +1 -1
  12. package/lib/engine/engine_gizmos.d.ts +11 -10
  13. package/lib/engine/engine_gizmos.js +24 -10
  14. package/lib/engine/engine_gizmos.js.map +1 -1
  15. package/lib/engine/extensions/extension_utils.js +1 -1
  16. package/lib/engine/extensions/extension_utils.js.map +1 -1
  17. package/lib/engine/xr/NeedleXRController.d.ts +3 -3
  18. package/lib/engine/xr/NeedleXRController.js +28 -0
  19. package/lib/engine/xr/NeedleXRController.js.map +1 -1
  20. package/lib/engine-components/codegen/components.d.ts +1 -1
  21. package/lib/engine-components/codegen/components.js +1 -1
  22. package/lib/engine-components/codegen/components.js.map +1 -1
  23. package/lib/engine-components/debug/LogStats.d.ts +1 -0
  24. package/lib/engine-components/debug/LogStats.js +1 -0
  25. package/lib/engine-components/debug/LogStats.js.map +1 -1
  26. package/lib/engine-components/timeline/PlayableDirector.js +1 -1
  27. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  28. package/lib/engine-components/timeline/TimelineTracks.d.ts +2 -1
  29. package/lib/engine-components/timeline/TimelineTracks.js +24 -19
  30. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  31. package/lib/engine-components/web/ScrollFollow.js +36 -34
  32. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  33. package/lib/engine-components/web/ViewBox.d.ts +14 -2
  34. package/lib/engine-components/web/ViewBox.js +39 -23
  35. package/lib/engine-components/web/ViewBox.js.map +1 -1
  36. package/lib/engine-components-experimental/Presentation.d.ts +1 -0
  37. package/lib/engine-components-experimental/Presentation.js +1 -0
  38. package/lib/engine-components-experimental/Presentation.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/engine/codegen/register_types.ts +1 -1
  41. package/src/engine/engine_camera.ts +5 -7
  42. package/src/engine/engine_gizmos.ts +37 -23
  43. package/src/engine/extensions/extension_utils.ts +1 -1
  44. package/src/engine/xr/NeedleXRController.ts +36 -4
  45. package/src/engine-components/codegen/components.ts +1 -1
  46. package/src/engine-components/debug/LogStats.ts +1 -0
  47. package/src/engine-components/timeline/PlayableDirector.ts +1 -1
  48. package/src/engine-components/timeline/TimelineTracks.ts +24 -19
  49. package/src/engine-components/web/ScrollFollow.ts +40 -36
  50. package/src/engine-components/web/ViewBox.ts +45 -27
  51. package/src/engine-components-experimental/Presentation.ts +1 -0
@@ -1,7 +1,7 @@
1
1
  import { Box3, Object3D } from "three";
2
2
  import { element } from "three/src/nodes/TSL.js";
3
3
 
4
- import { Context } from "../../engine/engine_context.js";
4
+ import { isDevEnvironment } from "../../engine/debug/debug.js";
5
5
  import { Mathf } from "../../engine/engine_math.js";
6
6
  import { serializable } from "../../engine/engine_serialization.js";
7
7
  import { getBoundingBox, setVisibleInCustomShadowRendering } from "../../engine/engine_three_utils.js";
@@ -174,8 +174,8 @@ export class ScrollFollow extends Behaviour {
174
174
 
175
175
  const value = this.invert ? 1 - this._current_value : this._current_value;
176
176
 
177
- const height = this._rangeEndValue - this._rangeStartValue;
178
- const pixelValue = this._rangeStartValue + value * height;
177
+ // const height = this._rangeEndValue - this._rangeStartValue;
178
+ // const pixelValue = this._rangeStartValue + value * height;
179
179
 
180
180
  // apply scroll to target(s)
181
181
  if (Array.isArray(this.target)) {
@@ -186,7 +186,7 @@ export class ScrollFollow extends Behaviour {
186
186
  }
187
187
 
188
188
  if (debug && this.context.time.frame % 30 === 0) {
189
- console.debug(`[ScrollFollow] ${this._current_value.toFixed(5)} — ${(this._target_value * 100).toFixed(0)}%`);
189
+ console.debug(`[ScrollFollow] ${this._current_value.toFixed(5)} — ${(this._target_value * 100).toFixed(0)}%, targets [${Array.isArray(this.target) ? this.target.length : 1}]`);
190
190
  }
191
191
  }
192
192
  }
@@ -304,13 +304,15 @@ export class ScrollFollow extends Behaviour {
304
304
  const index = markerIndex++;
305
305
 
306
306
  // Get marker elements from DOM
307
- if ((marker.element === undefined || marker.needsUpdate === true || /** element is not in DOM anymore? */ (!marker.element?.parentNode))) {
307
+ if ((marker.element === undefined || marker.needsUpdate === true || /** element is not in DOM anymore? */ (marker.element && !marker.element?.parentNode))) {
308
308
  marker.needsUpdate = false;
309
309
  try {
310
+ // TODO: with this it's currently not possible to remap markers from HTML. For example if I have two sections and I want to now use the marker["center"] multiple times to stay at that marker for a longer time
310
311
  marker.element = tryGetElementsForSelector(index, marker.name) as HTMLElement | null;
311
- if (debug) console.debug("ScrollMarker found on page", marker.element, marker.name);
312
+ if (debug) console.debug(`ScrollMarker #${index} "${marker.name}" (${marker.time.toFixed(2)}) found`, marker.element);
312
313
  if (!marker.element) {
313
314
  marker.timeline = undefined;
315
+ if (debug || isDevEnvironment()) console.warn(`No HTML element found for ScrollMarker: ${marker.name} (index ${index})`);
314
316
  continue;
315
317
  }
316
318
  else {
@@ -370,19 +372,20 @@ export class ScrollFollow extends Behaviour {
370
372
  const time01 = calculateTimelinePositionNormalized(timeline);
371
373
  // remap 0-1 to 0 - 1 - 0 (full weight at center)
372
374
  const weight = 1 - Math.abs(time01 - 0.5) * 2;
375
+ const name = marker.name || `marker${i}`;
373
376
  if (time01 > 0 && time01 <= 1) {
374
377
  const lerpTime = marker.time + (nextTime - marker.time) * time01;
375
- weightsArray.push({ time: lerpTime, weight: weight });
378
+ weightsArray.push({ name, time: lerpTime, weight: weight });
376
379
  sum += weight;
377
380
  }
378
381
  // Before the first marker is reached
379
382
  else if (i === 0 && time01 <= 0) {
380
- weightsArray.push({ time: 0, weight: 1 });
383
+ weightsArray.push({ name, time: 0, weight: 1 });
381
384
  sum += 1;
382
385
  }
383
386
  // After the last marker is reached
384
387
  else if (i === markersArray.length - 1 && time01 >= 1) {
385
- weightsArray.push({ time: duration, weight: 1 });
388
+ weightsArray.push({ name, time: duration, weight: 1 });
386
389
  sum += 1;
387
390
  }
388
391
  }
@@ -435,13 +438,16 @@ export class ScrollFollow extends Behaviour {
435
438
  time += diff * weight;
436
439
  }
437
440
  }
438
- if (debug && this.context.time.frame % 20 === 0) console.log(time.toFixed(3), [...weightsArray])
439
441
  if (this.damping <= 0) {
440
442
  director.time = time;
441
443
  }
442
444
  else {
443
445
  director.time = Mathf.lerp(director.time, time, this.context.time.deltaTime / this.damping);
444
446
  }
447
+
448
+ if (debug && this.context.time.frame % 30 === 0) {
449
+ console.log(`[ScrollFollow ] Timeline ${director.name}: ${time.toFixed(3)}`, weightsArray.map(w => `[${w.name} ${(w.weight * 100).toFixed(0)}%]`).join(", "));
450
+ }
445
451
  }
446
452
  }
447
453
 
@@ -450,9 +456,13 @@ export class ScrollFollow extends Behaviour {
450
456
 
451
457
 
452
458
  const weightsArray: OverlapInfo[] = [];
453
- const markersArray: (ScrollMarkerModel & { element?: HTMLElement | null, timeline?: ViewTimeline })[] = [];
459
+ const markersArray: Array<ScrollMarkerModel & {
460
+ element?: HTMLElement | null,
461
+ timeline?: ViewTimeline,
462
+ }> = [];
454
463
 
455
464
  type OverlapInfo = {
465
+ name: string,
456
466
  /** Marker time */
457
467
  time: number,
458
468
  /** Overlap in pixels */
@@ -468,17 +478,29 @@ type OverlapInfo = {
468
478
  // }
469
479
  // const querySelectorResults: Array<SelectorCache> = [];
470
480
 
471
- const needleScrollMarkerCacheKey = "data-timeline-marker";
472
481
  const needleScrollMarkerIndexCache = new Map<number, Element | null>();
473
482
  const needleScrollMarkerNameCache = new Map<string, Element | null>();
474
483
  let needsScrollMarkerRefresh = true;
475
484
 
476
- function tryGetElementsForSelector(index: number, name: string): Element | null {
485
+ function tryGetElementsForSelector(index: number, name: string, _cycle: number = 0): Element | null {
477
486
 
478
487
  if (!needsScrollMarkerRefresh) {
479
- let element = name?.length ? needleScrollMarkerNameCache.get(name) : null;
480
- if (element) return element;
481
- element = needleScrollMarkerIndexCache.get(index) || null;
488
+ if (name?.length) {
489
+ const element = needleScrollMarkerNameCache.get(name) || null;
490
+ if (element) return element;
491
+ // const isNumber = !isNaN(Number(name));
492
+ // if (!isNumber) {
493
+ // }
494
+ }
495
+ const element = needleScrollMarkerIndexCache.get(index) || null;
496
+ const value = element?.getAttribute("data-timeline-marker");
497
+ // if (value?.length) {
498
+ // if (cycle === 0) {
499
+ // // if the HTML marker we found by index does define a different marker name we try to find the correct HTML element by name
500
+ // return tryGetElementsForSelector(index, value, 1);
501
+ // }
502
+ // if (isDevEnvironment()) console.warn(`ScrollMarker name mismatch: expected "${name}", got "${value}"`);
503
+ // }
482
504
  return element;
483
505
  }
484
506
  needsScrollMarkerRefresh = false;
@@ -489,26 +511,8 @@ function tryGetElementsForSelector(index: number, name: string): Element | null
489
511
  const name = m.getAttribute("data-timeline-marker");
490
512
  if (name?.length) needleScrollMarkerNameCache.set(name, m);
491
513
  });
492
- const element = needleScrollMarkerIndexCache.get(index) || null;
493
- return element;
494
-
495
-
496
- /* e.g.
497
- <div class="section behind start" data-needle-scroll-marker>
498
- */
499
- // console.log(index, element)
500
- if (element) return element;
501
-
502
- // for (const entry of querySelectorResults) {
503
- // if (entry.selector === selector) {
504
- // const index = entry.usedElementCount++;
505
- // return entry.elements && index < entry.elements.length ? entry.elements[index] : null;
506
- // }
507
- // }
508
- // const elements = document.querySelectorAll(selector);
509
- // querySelectorResults.push({ selector, elements: Array.from(elements), usedElementCount: 1 });
510
- // if (elements.length > 0) return elements[0];
511
- return null;
514
+ needsScrollMarkerRefresh = false;
515
+ return tryGetElementsForSelector(index, name);
512
516
  }
513
517
 
514
518
 
@@ -1,54 +1,66 @@
1
- import { Camera, PerspectiveCamera, Vector2, Vector3 } from "three";
1
+ import { Camera, PerspectiveCamera, Quaternion, Scene, Vector2, Vector3 } from "three";
2
2
 
3
3
  import { isDevEnvironment } from "../../engine/debug/debug.js";
4
4
  import { Gizmos } from "../../engine/engine_gizmos.js";
5
5
  import { serializable } from "../../engine/engine_serialization_decorator.js";
6
- import { getTempQuaternion, getTempVector } from "../../engine/engine_three_utils.js";
6
+ import { getTempVector } from "../../engine/engine_three_utils.js";
7
7
  import { registerType } from "../../engine/engine_typestore.js";
8
8
  import { getParam } from "../../engine/engine_utils.js";
9
+ import { RGBAColor } from "../../engine/js-extensions/RGBAColor.js";
9
10
  import { Behaviour } from "../Component.js";
10
11
 
12
+
11
13
  const debugParam = getParam("debugviewbox");
14
+ const disabledGizmoColor = new RGBAColor(.5, .5, .5, .5);
12
15
 
16
+ /**
17
+ * This component can be used to automatically fit a certain box area into the camera view - no matter your screen size or aspect ratio.
18
+ *
19
+ * Add the ViewBox to an object into your scene
20
+ */
13
21
  @registerType
14
22
  export class ViewBox extends Behaviour {
15
23
 
16
- static instances: ViewBox[] = [];
24
+ static readonly instances: ViewBox[] = [];
17
25
 
26
+ /**
27
+ * The reference field of view is used to calculate the box size. This should usually be the same as your camera's fov.
28
+ * @default undefined (meaning it will use the camera fov on the first frame)
29
+ */
18
30
  @serializable()
19
- referenceFieldOfView: number = 60;
31
+ referenceFieldOfView: number | undefined = undefined;
20
32
 
33
+ /**
34
+ * Enable debug logs and rendering for this component instance
35
+ */
21
36
  @serializable()
22
37
  debug: boolean = false;
23
38
 
24
- // awake() {
25
- // // this.referenceFieldOfView = (this.context.mainCamera as PerspectiveCamera)?.fov || 60;
26
- // setInterval(()=>{
27
- // if(Math.random() > .5)
28
- // this.enabled = !this.enabled
29
- // }, 1000)
30
- // }
31
-
32
39
  onEnable(): void {
33
40
  if (debugParam || this.debug || isDevEnvironment()) console.debug("[ViewBox] Using camera fov:", this.referenceFieldOfView);
41
+ // register instance
34
42
  ViewBox.instances.push(this);
35
43
  }
36
44
 
37
45
  onDisable(): void {
38
46
  if (debugParam || this.debug) console.debug("[ViewBox] Disabled");
47
+ // unregister instance
39
48
  const idx = ViewBox.instances.indexOf(this);
40
49
  if (idx !== -1) ViewBox.instances.splice(idx, 1);
41
50
  this._projectedBoxElement?.remove();
42
51
  }
43
52
 
44
- onBeforeRender() {
53
+ onBeforeRender(): void {
45
54
  if (this.context.isInXR) return;
55
+ if (this.destroyed) return;
46
56
  const isActive = ViewBox.instances[ViewBox.instances.length - 1] === this;
47
57
  if (!isActive) {
48
- if (debugParam || this.debug) Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale, 0x333333);
58
+ if (debugParam || this.debug) {
59
+ Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale, disabledGizmoColor);
60
+ }
49
61
  return;
50
62
  }
51
- if (debugParam || this.debug) Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale, 0xdddd00);
63
+ if (debugParam || this.debug) Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale, 0xdddd00, 0, true, this.gameObject.worldQuaternion);
52
64
 
53
65
  // calculate box size to fit the camera frustrum size at the current position (just scale)
54
66
  const camera = this.context.mainCamera;
@@ -58,6 +70,10 @@ export class ViewBox extends Behaviour {
58
70
  return;
59
71
  }
60
72
 
73
+ if (this.referenceFieldOfView === undefined) {
74
+ this.referenceFieldOfView = camera.fov;
75
+ }
76
+
61
77
  if (this.referenceFieldOfView === undefined || this.referenceFieldOfView <= 0) {
62
78
  if (debugParam || this.debug) console.warn("[ViewBox] No valid referenceFieldOfView set, cannot adjust box size:", this.referenceFieldOfView);
63
79
  return;
@@ -94,6 +110,7 @@ export class ViewBox extends Behaviour {
94
110
  camera.fov = this.referenceFieldOfView;
95
111
  camera.updateProjectionMatrix();
96
112
 
113
+
97
114
  const boxPosition = this.gameObject.worldPosition;
98
115
  const boxScale = this.gameObject.worldScale;
99
116
 
@@ -138,7 +155,7 @@ export class ViewBox extends Behaviour {
138
155
  const height = 2 * Math.tan(vFOV / 2) * distance; // visible height
139
156
  const width = height * camera.aspect; // visible width
140
157
 
141
- const projectedBox = this.projectBoxIntoCamera(boxPosition, boxScale, camera, 1);
158
+ const projectedBox = this.projectBoxIntoCamera(camera, 1);
142
159
  // return
143
160
  const boxWidth = (projectedBox.maxX - projectedBox.minX);
144
161
  const boxHeight = (projectedBox.maxY - projectedBox.minY);
@@ -189,18 +206,18 @@ export class ViewBox extends Behaviour {
189
206
 
190
207
 
191
208
 
192
- private projectBoxIntoCamera(position: Vector3, scale: Vector3, camera: Camera, _factor: number) {
209
+ private projectBoxIntoCamera(camera: Camera, _factor: number) {
193
210
  const factor = .5 * _factor;
194
211
 
195
212
  const corners = [
196
- getTempVector(-scale.x * factor, -scale.y * factor, -scale.z * factor),
197
- getTempVector(scale.x * factor, -scale.y * factor, -scale.z * factor),
198
- getTempVector(-scale.x * factor, scale.y * factor, -scale.z * factor),
199
- getTempVector(scale.x * factor, scale.y * factor, -scale.z * factor),
200
- getTempVector(-scale.x * factor, -scale.y * factor, scale.z * factor),
201
- getTempVector(scale.x * factor, -scale.y * factor, scale.z * factor),
202
- getTempVector(-scale.x * factor, scale.y * factor, scale.z * factor),
203
- getTempVector(scale.x * factor, scale.y * factor, scale.z * factor),
213
+ getTempVector(-factor, -factor, -factor),
214
+ getTempVector(factor, -factor, -factor),
215
+ getTempVector(-factor, factor, -factor),
216
+ getTempVector(factor, factor, -factor),
217
+ getTempVector(-factor, -factor, factor),
218
+ getTempVector(factor, -factor, factor),
219
+ getTempVector(-factor, factor, factor),
220
+ getTempVector(factor, factor, factor),
204
221
  ];
205
222
  let minX = Number.POSITIVE_INFINITY;
206
223
  let maxX = Number.NEGATIVE_INFINITY;
@@ -208,7 +225,7 @@ export class ViewBox extends Behaviour {
208
225
  let maxY = Number.NEGATIVE_INFINITY;
209
226
  for (let i = 0; i < corners.length; i++) {
210
227
  const c = corners[i];
211
- c.add(position);
228
+ c.applyMatrix4(this.gameObject.matrixWorld);
212
229
  c.project(camera);
213
230
  if (c.x < minX) minX = c.x;
214
231
  if (c.x > maxX) maxX = c.x;
@@ -223,7 +240,8 @@ export class ViewBox extends Behaviour {
223
240
  if (this._projectedBoxElement.parentElement !== this.context.domElement)
224
241
  this.context.domElement.appendChild(this._projectedBoxElement);
225
242
  this._projectedBoxElement.style.position = "fixed";
226
- this._projectedBoxElement.style.outline = "5px dotted red";
243
+ // dotted but with larger gaps
244
+ this._projectedBoxElement.style.outline = "2px dashed rgba(255,0,0,.5)";
227
245
  this._projectedBoxElement.style.left = ((minX * .5 + .5) * this.context.domWidth) + "px";
228
246
  this._projectedBoxElement.style.top = ((-maxY * .5 + .5) * this.context.domHeight) + "px";
229
247
  this._projectedBoxElement.style.width = ((maxX - minX) * .5 * this.context.domWidth) + "px";
@@ -1,6 +1,7 @@
1
1
  import type { KeyCode } from "../engine/engine_input.js";
2
2
  import { Behaviour } from "../engine-components/Component.js";
3
3
 
4
+ /** @internal */
4
5
  export class PresentationMode extends Behaviour {
5
6
 
6
7
  toggleKey: KeyCode = "KeyP";