@needle-tools/engine 4.10.0-beta → 4.10.0-beta.2

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 (96) hide show
  1. package/components.needle.json +1 -1
  2. package/dist/{needle-engine.bundle-Dj6faVbC.js → needle-engine.bundle-BTgC7uAm.js} +7236 -7012
  3. package/dist/{needle-engine.bundle-42AmEGfk.umd.cjs → needle-engine.bundle-OTBqjiCd.umd.cjs} +152 -141
  4. package/dist/{needle-engine.bundle-C6zhyLF5.min.js → needle-engine.bundle-g2_JEHcF.min.js} +170 -159
  5. package/dist/needle-engine.js +259 -257
  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 +2 -0
  9. package/lib/engine/codegen/register_types.js.map +1 -1
  10. package/lib/engine/engine_camera.d.ts +7 -1
  11. package/lib/engine/engine_camera.fit.d.ts +1 -1
  12. package/lib/engine/engine_camera.fit.js +3 -30
  13. package/lib/engine/engine_camera.fit.js.map +1 -1
  14. package/lib/engine/engine_camera.js +46 -6
  15. package/lib/engine/engine_camera.js.map +1 -1
  16. package/lib/engine/engine_context.d.ts +6 -0
  17. package/lib/engine/engine_context.js +48 -9
  18. package/lib/engine/engine_context.js.map +1 -1
  19. package/lib/engine/engine_lightdata.d.ts +3 -3
  20. package/lib/engine/engine_lightdata.js +10 -10
  21. package/lib/engine/engine_lightdata.js.map +1 -1
  22. package/lib/engine/engine_physics_rapier.js +4 -0
  23. package/lib/engine/engine_physics_rapier.js.map +1 -1
  24. package/lib/engine/engine_scenelighting.d.ts +1 -1
  25. package/lib/engine/engine_scenelighting.js +4 -5
  26. package/lib/engine/engine_scenelighting.js.map +1 -1
  27. package/lib/engine/engine_utils.d.ts +3 -1
  28. package/lib/engine/engine_utils.js +11 -0
  29. package/lib/engine/engine_utils.js.map +1 -1
  30. package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
  31. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  32. package/lib/engine/webcomponents/logo-element.d.ts +1 -1
  33. package/lib/engine/webcomponents/logo-element.js +29 -5
  34. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  35. package/lib/engine/webcomponents/needle menu/needle-menu.js +4 -3
  36. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  37. package/lib/engine/webcomponents/needle-engine.js +22 -0
  38. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  39. package/lib/engine/webcomponents/needle-engine.loading.d.ts +0 -1
  40. package/lib/engine/webcomponents/needle-engine.loading.js +3 -36
  41. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  42. package/lib/engine-components/OrbitControls.d.ts +4 -1
  43. package/lib/engine-components/OrbitControls.js +30 -6
  44. package/lib/engine-components/OrbitControls.js.map +1 -1
  45. package/lib/engine-components/Renderer.js +6 -1
  46. package/lib/engine-components/Renderer.js.map +1 -1
  47. package/lib/engine-components/Skybox.js +22 -4
  48. package/lib/engine-components/Skybox.js.map +1 -1
  49. package/lib/engine-components/codegen/components.d.ts +1 -0
  50. package/lib/engine-components/codegen/components.js +1 -0
  51. package/lib/engine-components/codegen/components.js.map +1 -1
  52. package/lib/engine-components/timeline/PlayableDirector.d.ts +7 -0
  53. package/lib/engine-components/timeline/PlayableDirector.js +7 -0
  54. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  55. package/lib/engine-components/timeline/TimelineModels.d.ts +9 -1
  56. package/lib/engine-components/timeline/TimelineTracks.js +4 -2
  57. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  58. package/lib/engine-components/utils/LookAt.js +5 -1
  59. package/lib/engine-components/utils/LookAt.js.map +1 -1
  60. package/lib/engine-components/web/Clickthrough.js +10 -2
  61. package/lib/engine-components/web/Clickthrough.js.map +1 -1
  62. package/lib/engine-components/web/ScrollFollow.d.ts +22 -0
  63. package/lib/engine-components/web/ScrollFollow.js +159 -38
  64. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  65. package/lib/engine-components/web/ViewBox.d.ts +16 -0
  66. package/lib/engine-components/web/ViewBox.js +186 -0
  67. package/lib/engine-components/web/ViewBox.js.map +1 -0
  68. package/lib/engine-components/web/index.d.ts +1 -0
  69. package/lib/engine-components/web/index.js +1 -0
  70. package/lib/engine-components/web/index.js.map +1 -1
  71. package/package.json +1 -1
  72. package/src/engine/codegen/register_types.ts +2 -0
  73. package/src/engine/engine_camera.fit.ts +2 -32
  74. package/src/engine/engine_camera.ts +62 -8
  75. package/src/engine/engine_context.ts +50 -10
  76. package/src/engine/engine_lightdata.ts +11 -11
  77. package/src/engine/engine_physics_rapier.ts +3 -0
  78. package/src/engine/engine_scenelighting.ts +5 -6
  79. package/src/engine/engine_utils.ts +12 -0
  80. package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
  81. package/src/engine/webcomponents/logo-element.ts +29 -4
  82. package/src/engine/webcomponents/needle menu/needle-menu.ts +4 -3
  83. package/src/engine/webcomponents/needle-engine.loading.ts +32 -32
  84. package/src/engine/webcomponents/needle-engine.ts +33 -6
  85. package/src/engine-components/OrbitControls.ts +40 -1
  86. package/src/engine-components/Renderer.ts +6 -1
  87. package/src/engine-components/Skybox.ts +26 -7
  88. package/src/engine-components/codegen/components.ts +1 -0
  89. package/src/engine-components/timeline/PlayableDirector.ts +9 -0
  90. package/src/engine-components/timeline/TimelineModels.ts +9 -1
  91. package/src/engine-components/timeline/TimelineTracks.ts +4 -2
  92. package/src/engine-components/utils/LookAt.ts +5 -1
  93. package/src/engine-components/web/Clickthrough.ts +11 -2
  94. package/src/engine-components/web/ScrollFollow.ts +190 -44
  95. package/src/engine-components/web/ViewBox.ts +202 -0
  96. package/src/engine-components/web/index.ts +2 -1
@@ -1,13 +1,14 @@
1
1
  import { Box3, Object3D } from "three";
2
2
  import { element } from "three/src/nodes/TSL.js";
3
- import { Context } from "../../engine/engine_context.js";
4
3
 
4
+ import { Context } from "../../engine/engine_context.js";
5
5
  import { Mathf } from "../../engine/engine_math.js";
6
6
  import { serializable } from "../../engine/engine_serialization.js";
7
- import { getBoundingBox } from "../../engine/engine_three_utils.js";
7
+ import { getBoundingBox, setVisibleInCustomShadowRendering } from "../../engine/engine_three_utils.js";
8
8
  import { getParam } from "../../engine/engine_utils.js";
9
9
  import { Animation } from "../Animation.js";
10
10
  import { Animator } from "../Animator.js";
11
+ import { MarkerTrackHandler } from "../api.js";
11
12
  import { AudioSource } from "../AudioSource.js";
12
13
  import { Behaviour } from "../Component.js";
13
14
  import { EventList } from "../EventList.js";
@@ -150,7 +151,8 @@ export class ScrollFollow extends Behaviour {
150
151
  }
151
152
  }
152
153
 
153
- if (this._current_value !== this._appliedValue) {
154
+ // if (this._current_value !== this._appliedValue)
155
+ {
154
156
  this._appliedValue = this._current_value;
155
157
 
156
158
  let defaultPrevented = false;
@@ -242,7 +244,8 @@ export class ScrollFollow extends Behaviour {
242
244
 
243
245
  if (target instanceof PlayableDirector) {
244
246
  this.handleTimelineTarget(target, value);
245
- if (!target.isPlaying) target.evaluate();
247
+ if (target.isPlaying) target.pause();
248
+ target.evaluate();
246
249
  }
247
250
  else if (target instanceof Animator) {
248
251
  target.setFloat("scroll", value);
@@ -292,18 +295,35 @@ export class ScrollFollow extends Behaviour {
292
295
  let scrollRegionEnd = 0;
293
296
  markersArray.length = 0;
294
297
 
295
- for (const marker of director.foreachMarker<ScrollMarkerModel & { element?: HTMLElement | null, needsUpdate?: boolean }>("ScrollMarker")) {
298
+ // querySelectorResults.length = 0;
299
+ let markerIndex = 0;
300
+
301
+ // https://scroll-driven-animations.style/tools/view-timeline/ranges
302
+ for (const marker of director.foreachMarker<ScrollMarkerModel & { element?: HTMLElement | null, needsUpdate?: boolean, timeline?: ViewTimeline }>("ScrollMarker")) {
303
+
304
+ const index = markerIndex++;
296
305
 
297
306
  // Get marker elements from DOM
298
- if (marker.selector?.length && (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?.parentNode))) {
299
308
  marker.needsUpdate = false;
300
309
  try {
301
- marker.element = document.querySelector<HTMLElement>(marker.selector) || null;
302
- if (debug) console.debug("ScrollMarker found on page", marker.element, marker.selector);
310
+ marker.element = tryGetElementsForSelector(index, marker.name) as HTMLElement | null;
311
+ if (debug) console.debug("ScrollMarker found on page", marker.element, marker.name);
312
+ if (!marker.element) {
313
+ marker.timeline = undefined;
314
+ continue;
315
+ }
316
+ else {
317
+ /** @ts-ignore */
318
+ marker.timeline = new ViewTimeline({
319
+ subject: marker.element,
320
+ axis: 'block', // https://drafts.csswg.org/scroll-animations/#scroll-notation
321
+ });
322
+ }
303
323
  }
304
324
  catch (error) {
305
325
  marker.element = null;
306
- console.error("ScrollMarker selector is not valid: " + marker.selector + "\n", error);
326
+ console.error("ScrollMarker selector is not valid: " + marker.name + "\n", error);
307
327
  }
308
328
  }
309
329
 
@@ -330,41 +350,74 @@ export class ScrollFollow extends Behaviour {
330
350
 
331
351
  weightsArray.length = 0;
332
352
  let sum = 0;
353
+ const oneFrameTime = 1 / 60;
333
354
 
355
+ // We keep a separate count here in case there are some markers that could not be resolved so point to *invalid* elements - the timeline should fallback to 0-1 scroll behaviour then
334
356
  let markerCount = 0;
335
- for (const marker of markersArray) {
336
-
357
+ for (let i = 0; i < markersArray.length; i++) {
358
+ const marker = markersArray[i];
337
359
  if (!marker.element) continue;
360
+ const nextMarker = markersArray[i + 1];
338
361
 
339
- const top = marker.element.offsetTop;
340
- const height = marker.element.offsetHeight;
341
- const bottom = top + height;
342
- let overlap = 0;
343
-
344
- // TODO: if we have two marker sections where no HTML overlaps (vor example because some large section is between them) we probably want to still virtually interpolate between them slowly in that region
345
-
346
- if (bottom < currentTop) {
347
- // marker is above scroll region
348
- overlap = 0;
349
- }
350
- else if (top > currentBottom) {
351
- // marker is below scroll region
352
- overlap = 0;
353
- }
354
- else {
355
- // calculate overlap in pixels
356
- const overlapTop = Math.max(top, currentTop);
357
- const overlapBottom = Math.min(bottom, currentBottom);
358
- overlap = Math.max(0, overlapBottom - overlapTop);
359
- // console.log(marker.element.className, overlap)
360
- }
362
+ const nextTime = nextMarker
363
+ ? (nextMarker.time - oneFrameTime)
364
+ : duration;
361
365
 
362
366
  markerCount += 1;
363
367
 
364
- if (overlap > 0) {
365
- weightsArray.push({ time: marker.time, weight: overlap });
366
- sum += overlap;
368
+ const timeline = marker.timeline;
369
+ if (timeline) {
370
+ const time01 = calculateTimelinePositionNormalized(timeline);
371
+ // remap 0-1 to 0 - 1 - 0 (full weight at center)
372
+ const weight = 1 - Math.abs(time01 - 0.5) * 2;
373
+ if (time01 > 0 && time01 <= 1) {
374
+ const lerpTime = marker.time + (nextTime - marker.time) * time01;
375
+ weightsArray.push({ time: lerpTime, weight: weight });
376
+ sum += weight;
377
+ }
378
+ // Before the first marker is reached
379
+ else if (i === 0 && time01 <= 0) {
380
+ weightsArray.push({ time: 0, weight: 1 });
381
+ sum += 1;
382
+ }
383
+ // After the last marker is reached
384
+ else if (i === markersArray.length - 1 && time01 >= 1) {
385
+ weightsArray.push({ time: duration, weight: 1 });
386
+ sum += 1;
387
+ }
367
388
  }
389
+ continue;
390
+ // if(this.context.time.frame % 10 === 0) console.log(marker.element?.className, timeline, calculateTimelinePositionNormalized(timeline!));
391
+
392
+ // const top = marker.element.offsetTop - this._scrollContainerHeight;
393
+ // const height = marker.element.offsetHeight + this._scrollContainerHeight;
394
+ // const bottom = top + height;
395
+ // let overlap = 0;
396
+
397
+ // // TODO: if we have two marker sections where no HTML overlaps (vor example because some large section is between them) we probably want to still virtually interpolate between them slowly in that region
398
+
399
+ // if (bottom < currentTop) {
400
+ // // marker is above scroll region
401
+ // overlap = 0;
402
+ // }
403
+ // else if (top > currentBottom) {
404
+ // // marker is below scroll region
405
+ // overlap = 0;
406
+ // }
407
+ // else {
408
+ // // calculate overlap in pixels
409
+ // const overlapTop = Math.max(top, currentTop);
410
+ // const overlapBottom = Math.min(bottom, currentBottom);
411
+ // const height = Math.max(1, currentBottom - currentTop);
412
+ // overlap = Math.max(0, overlapBottom - overlapTop);
413
+ // }
414
+
415
+ // // if(this.context.time.frame % 20 === 0) console.log(overlap)
416
+
417
+ // if (overlap > 0) {
418
+ // weightsArray.push({ time: marker.time, weight: overlap });
419
+ // sum += overlap;
420
+ // }
368
421
  }
369
422
 
370
423
  if (weightsArray.length <= 0 && markerCount <= 0) {
@@ -372,25 +425,118 @@ export class ScrollFollow extends Behaviour {
372
425
  }
373
426
  else if (weightsArray.length > 0) {
374
427
  // normalize and calculate weighted time
375
- let time = weightsArray[0].time;
376
- for (const o of weightsArray) {
377
- const weight = o.weight / Math.max(0.00001, sum);
378
- // lerp time based on weight
379
- const diff = Math.abs(o.time - time);
380
- time += diff * weight;
428
+ let time = weightsArray[0].time; // fallback to first time
429
+ if (weightsArray.length > 1) {
430
+ for (const entry of weightsArray) {
431
+ const weight = entry.weight / Math.max(0.00001, sum);
432
+ // console.log(weight.toFixed(2))
433
+ // lerp time based on weight
434
+ const diff = Math.abs(entry.time - time);
435
+ time += diff * weight;
436
+ }
437
+ }
438
+ if (debug && this.context.time.frame % 20 === 0) console.log(time.toFixed(3), [...weightsArray])
439
+ if (this.damping <= 0) {
440
+ director.time = time;
441
+ }
442
+ else {
443
+ director.time = Mathf.lerp(director.time, time, this.context.time.deltaTime / this.damping);
381
444
  }
382
- director.time = time;
383
445
  }
384
446
  }
385
447
 
386
448
  }
387
449
 
450
+
451
+
388
452
  const weightsArray: OverlapInfo[] = [];
389
- const markersArray: (ScrollMarkerModel & { element?: HTMLElement | null })[] = [];
453
+ const markersArray: (ScrollMarkerModel & { element?: HTMLElement | null, timeline?: ViewTimeline })[] = [];
390
454
 
391
455
  type OverlapInfo = {
392
456
  /** Marker time */
393
457
  time: number,
394
458
  /** Overlap in pixels */
395
459
  weight: number,
460
+ }
461
+
462
+
463
+ // type SelectorCache = {
464
+ // /** The selector used to query the *elements */
465
+ // selector: string,
466
+ // elements: Element[] | null,
467
+ // usedElementCount: number,
468
+ // }
469
+ // const querySelectorResults: Array<SelectorCache> = [];
470
+
471
+ const needleScrollMarkerCacheKey = "data-timeline-marker";
472
+ const needleScrollMarkerIndexCache = new Map<number, Element | null>();
473
+ const needleScrollMarkerNameCache = new Map<string, Element | null>();
474
+ let needsScrollMarkerRefresh = true;
475
+
476
+ function tryGetElementsForSelector(index: number, name: string): Element | null {
477
+
478
+ if (!needsScrollMarkerRefresh) {
479
+ let element = name?.length ? needleScrollMarkerNameCache.get(name) : null;
480
+ if (element) return element;
481
+ element = needleScrollMarkerIndexCache.get(index) || null;
482
+ return element;
483
+ }
484
+ needsScrollMarkerRefresh = false;
485
+ needleScrollMarkerIndexCache.clear();
486
+ const markers = document.querySelectorAll(`[data-timeline-marker]`);
487
+ markers.forEach((m, i) => {
488
+ needleScrollMarkerIndexCache.set(i, m);
489
+ const name = m.getAttribute("data-timeline-marker");
490
+ if (name?.length) needleScrollMarkerNameCache.set(name, m);
491
+ });
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;
512
+ }
513
+
514
+
515
+ // #region ScrollTimeline
516
+
517
+ function calculateTimelinePositionNormalized(timeline: ViewTimeline) {
518
+ if (!timeline.source) return 0;
519
+ const currentTime = timeline.currentTime;
520
+ const duration = timeline.duration;
521
+ let durationValue = 1;
522
+ if (duration.unit === "seconds") {
523
+ durationValue = duration.value;
524
+ }
525
+ else if (duration.unit === "percent") {
526
+ durationValue = duration.value;
527
+ }
528
+ const t01 = currentTime.unit === "seconds" ? (currentTime.value / durationValue) : (currentTime.value / 100);
529
+ return t01;
530
+ }
531
+
532
+
533
+ declare global {
534
+ interface ViewTimeline {
535
+ axis: 'block' | 'inline';
536
+ currentTime: { unit: 'seconds' | 'percent', value: number };
537
+ duration: { unit: 'seconds' | 'percent', value: number };
538
+ source: Element | null;
539
+ startOffset: { unit: 'px', value: number };
540
+ endOffset: { unit: 'px', value: number };
541
+ }
396
542
  }
@@ -0,0 +1,202 @@
1
+ import { Camera, PerspectiveCamera, Vector2, Vector3 } from "three";
2
+
3
+ import { isDevEnvironment } from "../../engine/debug/debug.js";
4
+ import { Gizmos } from "../../engine/engine_gizmos.js";
5
+ import { serializable } from "../../engine/engine_serialization_decorator.js";
6
+ import { getTempVector } from "../../engine/engine_three_utils.js";
7
+ import { registerType } from "../../engine/engine_typestore.js";
8
+ import { getParam } from "../../engine/engine_utils.js";
9
+ import { Behaviour } from "../Component.js";
10
+
11
+ const debugParam = getParam("debugviewbox");
12
+
13
+ @registerType
14
+ export class ViewBox extends Behaviour {
15
+
16
+ static instances: ViewBox[] = [];
17
+
18
+ @serializable()
19
+ referenceFieldOfView: number = 60;
20
+
21
+ @serializable()
22
+ debug: boolean = false;
23
+
24
+ awake() {
25
+ // this.referenceFieldOfView = (this.context.mainCamera as PerspectiveCamera)?.fov || 60;
26
+ // setInterval(()=>{
27
+ // this.enabled = !this.enabled
28
+ // }, 1000)
29
+ }
30
+ onEnable(): void {
31
+ if (debugParam || this.debug || isDevEnvironment()) console.debug("[ViewBox] Using camera fov:", this.referenceFieldOfView);
32
+ ViewBox.instances.push(this);
33
+ }
34
+
35
+ onDisable(): void {
36
+ const idx = ViewBox.instances.indexOf(this);
37
+ if (idx !== -1) ViewBox.instances.splice(idx, 1);
38
+ }
39
+
40
+ onBeforeRender() {
41
+ if (this.context.isInXR) return;
42
+ const isActive = ViewBox.instances[ViewBox.instances.length - 1] === this;
43
+ if (!isActive) {
44
+ if (debugParam || this.debug) Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale, 0x333333);
45
+ return;
46
+ }
47
+ if (debugParam || this.debug) Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale, 0xdddd00);
48
+
49
+ // calculate box size to fit the camera frustrum size at the current position (just scale)
50
+ const camera = this.context.mainCamera;
51
+ if (!camera) return;
52
+ if (!(camera instanceof PerspectiveCamera)) {
53
+ // TODO: support orthographic camera
54
+ return;
55
+ }
56
+
57
+ if (this.referenceFieldOfView === undefined || this.referenceFieldOfView <= 0) {
58
+ if (debugParam || this.debug) console.warn("[ViewBox] No valid referenceFieldOfView set, cannot adjust box size:", this.referenceFieldOfView);
59
+ return;
60
+ }
61
+
62
+ const domWidth = this.context.domWidth;
63
+ const domHeight = this.context.domHeight;
64
+
65
+ let rectPosX = 0;
66
+ let rectPosY = 0;
67
+ let rectWidth = domWidth;
68
+ let rectHeight = domHeight;
69
+ let diffWidth = 1;
70
+ let diffHeight = 1;
71
+ // use focus rect if available
72
+ const focusRectSize = this.context.focusRectSize;
73
+ if (focusRectSize) {
74
+ // console.log(focusRectSize)
75
+ rectPosX = focusRectSize.x;
76
+ rectPosY = focusRectSize.y;
77
+ rectWidth = focusRectSize.width;
78
+ rectHeight = focusRectSize.height;
79
+ diffWidth = domWidth / rectWidth;
80
+ diffHeight = domHeight / rectHeight;
81
+ }
82
+
83
+ // const view = camera.view;
84
+ const view = camera.view;
85
+ const zoom = camera.zoom;
86
+ const aspect = camera.aspect;
87
+ const fov = camera.fov;
88
+ camera.view = null;
89
+ camera.zoom = 1;
90
+ // camera.aspect = rectWidth / rectHeight;
91
+ camera.fov = this.referenceFieldOfView;
92
+ camera.updateProjectionMatrix();
93
+
94
+ const boxPosition = this.gameObject.worldPosition;
95
+ const boxScale = this.gameObject.worldScale;
96
+
97
+
98
+
99
+ // const fov = this.referenceFieldOfView
100
+ const distance = camera.worldPosition.distanceTo(boxPosition);
101
+ const vFOV = this.referenceFieldOfView * Math.PI / 180; // convert vertical fov to radians
102
+ const height = 2 * Math.tan(vFOV / 2) * distance; // visible height
103
+ const width = height * camera.aspect; // visible width
104
+
105
+ const projectedBox = this.projectBoxIntoCamera(boxPosition, boxScale, camera, height * .5);
106
+ const boxWidth = (projectedBox.maxX - projectedBox.minX);
107
+ const boxHeight = (projectedBox.maxY - projectedBox.minY);
108
+
109
+ // TODO: take the rect size different into account
110
+ const scale = this.fit(
111
+ boxWidth * camera.aspect,
112
+ boxHeight,
113
+ width / diffWidth,
114
+ height / diffHeight
115
+ );
116
+ const vec = getTempVector(boxPosition);
117
+ vec.project(camera);
118
+ this.context.focusRectSettings.offsetX = vec.x;
119
+ this.context.focusRectSettings.offsetY = vec.y;
120
+ this.context.focusRectSettings.zoom = scale;
121
+ // if we don't have a focus rect yet, set it to the dom element
122
+ if (!this.context.focusRect) this.context.setCameraFocusRect(this.context.domElement);
123
+
124
+ // Reset values
125
+ camera.view = view;
126
+ camera.zoom = zoom;
127
+ camera.aspect = aspect;
128
+ camera.fov = fov;
129
+ // camera.updateProjectionMatrix();
130
+
131
+
132
+ // BACKLOG: some code for box scale of an object (different component)
133
+ // this.gameObject.worldScale = getTempVector(width, height, worldscale.z);
134
+ // this.gameObject.scale.multiplyScalar(.98)
135
+ // const minscale = Math.min(width, height);
136
+ // console.log(width, height);
137
+ // this.gameObject.worldScale = getTempVector(scale, scale, scale);
138
+ }
139
+
140
+
141
+ /**
142
+ * Cover fit
143
+ */
144
+ private fit(width1: number, height1: number, width2: number, height2: number) {
145
+ const scaleX = width2 / width1;
146
+ const scaleY = height2 / height1;
147
+ return Math.min(scaleX, scaleY);
148
+ }
149
+
150
+
151
+
152
+ private projectBoxIntoCamera(position: Vector3, scale: Vector3, camera: Camera, diff: number) {
153
+
154
+ const factor = .5 * diff;
155
+
156
+ const corners = [
157
+ getTempVector(-scale.x * factor, -scale.y * factor, -scale.z * factor),
158
+ getTempVector(scale.x * factor, -scale.y * factor, -scale.z * factor),
159
+ getTempVector(-scale.x * factor, scale.y * factor, -scale.z * factor),
160
+ getTempVector(scale.x * factor, scale.y * factor, -scale.z * factor),
161
+ getTempVector(-scale.x * factor, -scale.y * factor, scale.z * factor),
162
+ getTempVector(scale.x * factor, -scale.y * factor, scale.z * factor),
163
+ getTempVector(-scale.x * factor, scale.y * factor, scale.z * factor),
164
+ getTempVector(scale.x * factor, scale.y * factor, scale.z * factor),
165
+ ];
166
+ let minX = Number.POSITIVE_INFINITY;
167
+ let maxX = Number.NEGATIVE_INFINITY;
168
+ let minY = Number.POSITIVE_INFINITY;
169
+ let maxY = Number.NEGATIVE_INFINITY;
170
+ for (let i = 0; i < corners.length; i++) {
171
+ const c = corners[i];
172
+ c.add(position);
173
+ c.project(camera);
174
+ if (c.x < minX) minX = c.x;
175
+ if (c.x > maxX) maxX = c.x;
176
+ if (c.y < minY) minY = c.y;
177
+ if (c.y > maxY) maxY = c.y;
178
+ }
179
+
180
+ // if(!this._projectedBoxElement) {
181
+ // this._projectedBoxElement = document.createElement("div");
182
+ // this.context.domElement.appendChild(this._projectedBoxElement);
183
+ // }
184
+ // this._projectedBoxElement.style.position = "fixed";
185
+ // this._projectedBoxElement.style.outline = "10px solid red";
186
+ // this._projectedBoxElement.style.left = ((minX * .5 + .5) * this.context.domWidth) + "px";
187
+ // this._projectedBoxElement.style.top = ((-maxY * .5 + .5) * this.context.domHeight) + "px";
188
+ // this._projectedBoxElement.style.width = ((maxX - minX) * .5 * this.context.domWidth) + "px";
189
+ // this._projectedBoxElement.style.height = ((maxY - minY) * .5 * this.context.domHeight) + "px";
190
+ // this._projectedBoxElement.style.pointerEvents = "none";
191
+ // this._projectedBoxElement.style.zIndex = "1000";
192
+
193
+
194
+ return { minX, maxX, minY, maxY };
195
+
196
+ }
197
+ private _projectedBoxElement: HTMLElement | null = null;
198
+
199
+
200
+
201
+
202
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./Clickthrough.js";
2
2
  export * from "./CursorFollow.js";
3
3
  export * from "./HoverAnimation.js";
4
- export * from "./ScrollFollow.js";
4
+ export * from "./ScrollFollow.js";
5
+ export * from "./ViewBox.js";