@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.
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-Dj6faVbC.js → needle-engine.bundle-BTgC7uAm.js} +7236 -7012
- package/dist/{needle-engine.bundle-42AmEGfk.umd.cjs → needle-engine.bundle-OTBqjiCd.umd.cjs} +152 -141
- package/dist/{needle-engine.bundle-C6zhyLF5.min.js → needle-engine.bundle-g2_JEHcF.min.js} +170 -159
- package/dist/needle-engine.js +259 -257
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/codegen/register_types.js +2 -0
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_camera.d.ts +7 -1
- package/lib/engine/engine_camera.fit.d.ts +1 -1
- package/lib/engine/engine_camera.fit.js +3 -30
- package/lib/engine/engine_camera.fit.js.map +1 -1
- package/lib/engine/engine_camera.js +46 -6
- package/lib/engine/engine_camera.js.map +1 -1
- package/lib/engine/engine_context.d.ts +6 -0
- package/lib/engine/engine_context.js +48 -9
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_lightdata.d.ts +3 -3
- package/lib/engine/engine_lightdata.js +10 -10
- package/lib/engine/engine_lightdata.js.map +1 -1
- package/lib/engine/engine_physics_rapier.js +4 -0
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_scenelighting.d.ts +1 -1
- package/lib/engine/engine_scenelighting.js +4 -5
- package/lib/engine/engine_scenelighting.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +3 -1
- package/lib/engine/engine_utils.js +11 -0
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
- package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
- package/lib/engine/webcomponents/logo-element.d.ts +1 -1
- package/lib/engine/webcomponents/logo-element.js +29 -5
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +4 -3
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.js +22 -0
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.d.ts +0 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +3 -36
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +4 -1
- package/lib/engine-components/OrbitControls.js +30 -6
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/Renderer.js +6 -1
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/Skybox.js +22 -4
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +1 -0
- package/lib/engine-components/codegen/components.js +1 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +7 -0
- package/lib/engine-components/timeline/PlayableDirector.js +7 -0
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/TimelineModels.d.ts +9 -1
- package/lib/engine-components/timeline/TimelineTracks.js +4 -2
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/utils/LookAt.js +5 -1
- package/lib/engine-components/utils/LookAt.js.map +1 -1
- package/lib/engine-components/web/Clickthrough.js +10 -2
- package/lib/engine-components/web/Clickthrough.js.map +1 -1
- package/lib/engine-components/web/ScrollFollow.d.ts +22 -0
- package/lib/engine-components/web/ScrollFollow.js +159 -38
- package/lib/engine-components/web/ScrollFollow.js.map +1 -1
- package/lib/engine-components/web/ViewBox.d.ts +16 -0
- package/lib/engine-components/web/ViewBox.js +186 -0
- package/lib/engine-components/web/ViewBox.js.map +1 -0
- package/lib/engine-components/web/index.d.ts +1 -0
- package/lib/engine-components/web/index.js +1 -0
- package/lib/engine-components/web/index.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/codegen/register_types.ts +2 -0
- package/src/engine/engine_camera.fit.ts +2 -32
- package/src/engine/engine_camera.ts +62 -8
- package/src/engine/engine_context.ts +50 -10
- package/src/engine/engine_lightdata.ts +11 -11
- package/src/engine/engine_physics_rapier.ts +3 -0
- package/src/engine/engine_scenelighting.ts +5 -6
- package/src/engine/engine_utils.ts +12 -0
- package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
- package/src/engine/webcomponents/logo-element.ts +29 -4
- package/src/engine/webcomponents/needle menu/needle-menu.ts +4 -3
- package/src/engine/webcomponents/needle-engine.loading.ts +32 -32
- package/src/engine/webcomponents/needle-engine.ts +33 -6
- package/src/engine-components/OrbitControls.ts +40 -1
- package/src/engine-components/Renderer.ts +6 -1
- package/src/engine-components/Skybox.ts +26 -7
- package/src/engine-components/codegen/components.ts +1 -0
- package/src/engine-components/timeline/PlayableDirector.ts +9 -0
- package/src/engine-components/timeline/TimelineModels.ts +9 -1
- package/src/engine-components/timeline/TimelineTracks.ts +4 -2
- package/src/engine-components/utils/LookAt.ts +5 -1
- package/src/engine-components/web/Clickthrough.ts +11 -2
- package/src/engine-components/web/ScrollFollow.ts +190 -44
- package/src/engine-components/web/ViewBox.ts +202 -0
- 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 (
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
302
|
-
if (debug) console.debug("ScrollMarker found on page", marker.element, marker.
|
|
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.
|
|
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 (
|
|
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
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
+
}
|