@needle-tools/engine 4.10.0-next.f0ec242 → 4.10.0
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/CHANGELOG.md +7 -3
- package/README.md +2 -1
- package/components.needle.json +1 -1
- package/dist/needle-engine.bundle-BSq-d_16.min.js +1652 -0
- package/dist/{needle-engine.bundle-dgNq9Vsa.umd.cjs → needle-engine.bundle-C2kVfQq6.umd.cjs} +153 -140
- package/dist/{needle-engine.bundle-BC-0Ex9m.js → needle-engine.bundle-CIuhf7-t.js} +7388 -7113
- package/dist/needle-engine.d.ts +15 -15
- package/dist/needle-engine.js +259 -257
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/vendor-CPuBPspY.umd.cjs +1121 -0
- package/dist/vendor-DPCU8cUF.min.js +1121 -0
- package/dist/vendor-MBoqSyFm.js +16240 -0
- 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.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_gizmos.d.ts +11 -10
- package/lib/engine/engine_gizmos.js +24 -10
- package/lib/engine/engine_gizmos.js.map +1 -1
- package/lib/engine/engine_license.js +1 -1
- package/lib/engine/engine_license.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/extensions/extension_utils.js +1 -1
- package/lib/engine/extensions/extension_utils.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/xr/NeedleXRController.d.ts +3 -3
- package/lib/engine/xr/NeedleXRController.js +28 -0
- package/lib/engine/xr/NeedleXRController.js.map +1 -1
- package/lib/engine-components/CameraUtils.js +2 -1
- package/lib/engine-components/CameraUtils.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/debug/LogStats.d.ts +1 -0
- package/lib/engine-components/debug/LogStats.js +1 -0
- package/lib/engine-components/debug/LogStats.js.map +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +7 -0
- package/lib/engine-components/timeline/PlayableDirector.js +8 -1
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/TimelineModels.d.ts +11 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +2 -1
- package/lib/engine-components/timeline/TimelineTracks.js +30 -25
- 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 +24 -0
- package/lib/engine-components/web/ScrollFollow.js +169 -42
- package/lib/engine-components/web/ScrollFollow.js.map +1 -1
- package/lib/engine-components/web/ViewBox.d.ts +43 -0
- package/lib/engine-components/web/ViewBox.js +258 -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/lib/engine-components-experimental/Presentation.d.ts +1 -0
- package/lib/engine-components-experimental/Presentation.js +1 -0
- package/lib/engine-components-experimental/Presentation.js.map +1 -1
- package/package.json +3 -2
- package/src/engine/codegen/register_types.ts +2 -0
- package/src/engine/engine_camera.ts +61 -9
- package/src/engine/engine_context.ts +50 -10
- package/src/engine/engine_gizmos.ts +37 -23
- package/src/engine/engine_license.ts +1 -1
- 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/extensions/extension_utils.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/xr/NeedleXRController.ts +36 -4
- package/src/engine-components/CameraUtils.ts +1 -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/debug/LogStats.ts +1 -0
- package/src/engine-components/timeline/PlayableDirector.ts +10 -1
- package/src/engine-components/timeline/TimelineModels.ts +11 -1
- package/src/engine-components/timeline/TimelineTracks.ts +30 -25
- 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 +205 -51
- package/src/engine-components/web/ViewBox.ts +278 -0
- package/src/engine-components/web/index.ts +2 -1
- package/src/engine-components-experimental/Presentation.ts +1 -0
- package/dist/needle-engine.bundle-BSh7dSEx.min.js +0 -1639
- package/dist/vendor-D0Yvltn9.umd.cjs +0 -1121
- package/dist/vendor-DU8tJyl_.js +0 -14366
- package/dist/vendor-JyrX4DVM.min.js +0 -1121
|
@@ -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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
846
|
+
this.needsSorting = true;
|
|
845
847
|
}
|
|
846
848
|
|
|
847
849
|
evaluate(_time: number) {
|
|
848
|
-
if (this.
|
|
849
|
-
|
|
850
|
-
|
|
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)
|
|
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
|
|
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
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
// For firefox ViewTimeline support
|
|
2
|
+
import "scroll-timeline-polyfill/dist/scroll-timeline.js";
|
|
3
|
+
|
|
1
4
|
import { Box3, Object3D } from "three";
|
|
2
|
-
import { element } from "three/src/nodes/TSL.js";
|
|
3
5
|
|
|
4
|
-
import {
|
|
6
|
+
import { isDevEnvironment } from "../../engine/debug/debug.js";
|
|
5
7
|
import { Mathf } from "../../engine/engine_math.js";
|
|
6
8
|
import { serializable } from "../../engine/engine_serialization.js";
|
|
7
|
-
import { getBoundingBox } from "../../engine/engine_three_utils.js";
|
|
9
|
+
import { getBoundingBox, setVisibleInCustomShadowRendering } from "../../engine/engine_three_utils.js";
|
|
8
10
|
import { getParam } from "../../engine/engine_utils.js";
|
|
9
11
|
import { Animation } from "../Animation.js";
|
|
10
12
|
import { Animator } from "../Animator.js";
|
|
@@ -38,6 +40,7 @@ type ScrollFollowEvent = {
|
|
|
38
40
|
*
|
|
39
41
|
* @link Example at https://scrollytelling-2-z23hmxby7c6x-u30ld.needle.run/
|
|
40
42
|
* @link Template at https://github.com/needle-engine/scrollytelling-template
|
|
43
|
+
* @link [Scrollytelling Bike Demo](https://scrollytelling-bike-z23hmxb2gnu5a.needle.run/)
|
|
41
44
|
*
|
|
42
45
|
* ## How to use with an Animator
|
|
43
46
|
* 1. Create an Animator component and set up a float parameter named "scroll".
|
|
@@ -150,7 +153,8 @@ export class ScrollFollow extends Behaviour {
|
|
|
150
153
|
}
|
|
151
154
|
}
|
|
152
155
|
|
|
153
|
-
if (this._current_value !== this._appliedValue)
|
|
156
|
+
// if (this._current_value !== this._appliedValue)
|
|
157
|
+
{
|
|
154
158
|
this._appliedValue = this._current_value;
|
|
155
159
|
|
|
156
160
|
let defaultPrevented = false;
|
|
@@ -172,8 +176,8 @@ export class ScrollFollow extends Behaviour {
|
|
|
172
176
|
|
|
173
177
|
const value = this.invert ? 1 - this._current_value : this._current_value;
|
|
174
178
|
|
|
175
|
-
const height = this._rangeEndValue - this._rangeStartValue;
|
|
176
|
-
const pixelValue = this._rangeStartValue + value * height;
|
|
179
|
+
// const height = this._rangeEndValue - this._rangeStartValue;
|
|
180
|
+
// const pixelValue = this._rangeStartValue + value * height;
|
|
177
181
|
|
|
178
182
|
// apply scroll to target(s)
|
|
179
183
|
if (Array.isArray(this.target)) {
|
|
@@ -184,7 +188,7 @@ export class ScrollFollow extends Behaviour {
|
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
if (debug && this.context.time.frame % 30 === 0) {
|
|
187
|
-
console.debug(`[ScrollFollow] ${this._current_value.toFixed(5)} — ${(this._target_value * 100).toFixed(0)}
|
|
191
|
+
console.debug(`[ScrollFollow] ${this._current_value.toFixed(5)} — ${(this._target_value * 100).toFixed(0)}%, targets [${Array.isArray(this.target) ? this.target.length : 1}]`);
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
}
|
|
@@ -242,7 +246,8 @@ export class ScrollFollow extends Behaviour {
|
|
|
242
246
|
|
|
243
247
|
if (target instanceof PlayableDirector) {
|
|
244
248
|
this.handleTimelineTarget(target, value);
|
|
245
|
-
if (
|
|
249
|
+
if (target.isPlaying) target.pause();
|
|
250
|
+
target.evaluate();
|
|
246
251
|
}
|
|
247
252
|
else if (target instanceof Animator) {
|
|
248
253
|
target.setFloat("scroll", value);
|
|
@@ -292,18 +297,37 @@ export class ScrollFollow extends Behaviour {
|
|
|
292
297
|
let scrollRegionEnd = 0;
|
|
293
298
|
markersArray.length = 0;
|
|
294
299
|
|
|
295
|
-
|
|
300
|
+
// querySelectorResults.length = 0;
|
|
301
|
+
let markerIndex = 0;
|
|
302
|
+
|
|
303
|
+
// https://scroll-driven-animations.style/tools/view-timeline/ranges
|
|
304
|
+
for (const marker of director.foreachMarker<ScrollMarkerModel & { element?: HTMLElement | null, needsUpdate?: boolean, timeline?: ViewTimeline }>("ScrollMarker")) {
|
|
305
|
+
|
|
306
|
+
const index = markerIndex++;
|
|
296
307
|
|
|
297
308
|
// Get marker elements from DOM
|
|
298
|
-
if (
|
|
309
|
+
if ((marker.element === undefined || marker.needsUpdate === true || /** element is not in DOM anymore? */ (marker.element && !marker.element?.parentNode))) {
|
|
299
310
|
marker.needsUpdate = false;
|
|
300
311
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
312
|
+
// 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
|
|
313
|
+
marker.element = tryGetElementsForSelector(index, marker.name) as HTMLElement | null;
|
|
314
|
+
if (debug) console.debug(`ScrollMarker #${index} "${marker.name}" (${marker.time.toFixed(2)}) found`, marker.element);
|
|
315
|
+
if (!marker.element) {
|
|
316
|
+
marker.timeline = undefined;
|
|
317
|
+
if (debug || isDevEnvironment()) console.warn(`No HTML element found for ScrollMarker: ${marker.name} (index ${index})`);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
/** @ts-ignore */
|
|
322
|
+
marker.timeline = new ViewTimeline({
|
|
323
|
+
subject: marker.element,
|
|
324
|
+
axis: 'block', // https://drafts.csswg.org/scroll-animations/#scroll-notation
|
|
325
|
+
});
|
|
326
|
+
}
|
|
303
327
|
}
|
|
304
328
|
catch (error) {
|
|
305
329
|
marker.element = null;
|
|
306
|
-
console.error("ScrollMarker selector is not valid: " + marker.
|
|
330
|
+
console.error("ScrollMarker selector is not valid: " + marker.name + "\n", error);
|
|
307
331
|
}
|
|
308
332
|
}
|
|
309
333
|
|
|
@@ -330,65 +354,195 @@ export class ScrollFollow extends Behaviour {
|
|
|
330
354
|
|
|
331
355
|
weightsArray.length = 0;
|
|
332
356
|
let sum = 0;
|
|
357
|
+
const oneFrameTime = 1 / 60;
|
|
333
358
|
|
|
359
|
+
// 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
360
|
let markerCount = 0;
|
|
335
|
-
for (
|
|
336
|
-
|
|
361
|
+
for (let i = 0; i < markersArray.length; i++) {
|
|
362
|
+
const marker = markersArray[i];
|
|
337
363
|
if (!marker.element) continue;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
364
|
+
const nextMarker = markersArray[i + 1];
|
|
365
|
+
|
|
366
|
+
const nextTime = nextMarker
|
|
367
|
+
? (nextMarker.time - oneFrameTime)
|
|
368
|
+
: duration;
|
|
369
|
+
|
|
370
|
+
markerCount += 1;
|
|
371
|
+
|
|
372
|
+
const timeline = marker.timeline;
|
|
373
|
+
if (timeline) {
|
|
374
|
+
const time01 = calculateTimelinePositionNormalized(timeline);
|
|
375
|
+
// remap 0-1 to 0 - 1 - 0 (full weight at center)
|
|
376
|
+
const weight = 1 - Math.abs(time01 - 0.5) * 2;
|
|
377
|
+
const name = marker.name || `marker${i}`;
|
|
378
|
+
if (time01 > 0 && time01 <= 1) {
|
|
379
|
+
const lerpTime = marker.time + (nextTime - marker.time) * time01;
|
|
380
|
+
weightsArray.push({ name, time: lerpTime, weight: weight });
|
|
381
|
+
sum += weight;
|
|
382
|
+
}
|
|
383
|
+
// Before the first marker is reached
|
|
384
|
+
else if (i === 0 && time01 <= 0) {
|
|
385
|
+
weightsArray.push({ name, time: 0, weight: 1 });
|
|
386
|
+
sum += 1;
|
|
387
|
+
}
|
|
388
|
+
// After the last marker is reached
|
|
389
|
+
else if (i === markersArray.length - 1 && time01 >= 1) {
|
|
390
|
+
weightsArray.push({ name, time: duration, weight: 1 });
|
|
391
|
+
sum += 1;
|
|
392
|
+
}
|
|
365
393
|
}
|
|
394
|
+
continue;
|
|
395
|
+
// if(this.context.time.frame % 10 === 0) console.log(marker.element?.className, timeline, calculateTimelinePositionNormalized(timeline!));
|
|
396
|
+
|
|
397
|
+
// const top = marker.element.offsetTop - this._scrollContainerHeight;
|
|
398
|
+
// const height = marker.element.offsetHeight + this._scrollContainerHeight;
|
|
399
|
+
// const bottom = top + height;
|
|
400
|
+
// let overlap = 0;
|
|
401
|
+
|
|
402
|
+
// // 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
|
|
403
|
+
|
|
404
|
+
// if (bottom < currentTop) {
|
|
405
|
+
// // marker is above scroll region
|
|
406
|
+
// overlap = 0;
|
|
407
|
+
// }
|
|
408
|
+
// else if (top > currentBottom) {
|
|
409
|
+
// // marker is below scroll region
|
|
410
|
+
// overlap = 0;
|
|
411
|
+
// }
|
|
412
|
+
// else {
|
|
413
|
+
// // calculate overlap in pixels
|
|
414
|
+
// const overlapTop = Math.max(top, currentTop);
|
|
415
|
+
// const overlapBottom = Math.min(bottom, currentBottom);
|
|
416
|
+
// const height = Math.max(1, currentBottom - currentTop);
|
|
417
|
+
// overlap = Math.max(0, overlapBottom - overlapTop);
|
|
418
|
+
// }
|
|
419
|
+
|
|
420
|
+
// // if(this.context.time.frame % 20 === 0) console.log(overlap)
|
|
421
|
+
|
|
422
|
+
// if (overlap > 0) {
|
|
423
|
+
// weightsArray.push({ time: marker.time, weight: overlap });
|
|
424
|
+
// sum += overlap;
|
|
425
|
+
// }
|
|
366
426
|
}
|
|
367
427
|
|
|
368
|
-
if (weightsArray.length <= 0 &&
|
|
428
|
+
if (weightsArray.length <= 0 && markerCount <= 0) {
|
|
369
429
|
director.time = value * duration;
|
|
370
430
|
}
|
|
371
431
|
else if (weightsArray.length > 0) {
|
|
372
432
|
// normalize and calculate weighted time
|
|
373
|
-
let time = weightsArray[0].time;
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
433
|
+
let time = weightsArray[0].time; // fallback to first time
|
|
434
|
+
if (weightsArray.length > 1) {
|
|
435
|
+
for (const entry of weightsArray) {
|
|
436
|
+
const weight = entry.weight / Math.max(0.00001, sum);
|
|
437
|
+
// console.log(weight.toFixed(2))
|
|
438
|
+
// lerp time based on weight
|
|
439
|
+
const diff = Math.abs(entry.time - time);
|
|
440
|
+
time += diff * weight;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (this.damping <= 0) {
|
|
444
|
+
director.time = time;
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
director.time = Mathf.lerp(director.time, time, this.context.time.deltaTime / this.damping);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (debug && this.context.time.frame % 30 === 0) {
|
|
451
|
+
console.log(`[ScrollFollow ] Timeline ${director.name}: ${time.toFixed(3)}`, weightsArray.map(w => `[${w.name} ${(w.weight * 100).toFixed(0)}%]`).join(", "));
|
|
379
452
|
}
|
|
380
|
-
director.time = time;
|
|
381
453
|
}
|
|
382
454
|
}
|
|
383
455
|
|
|
384
456
|
}
|
|
385
457
|
|
|
458
|
+
|
|
459
|
+
|
|
386
460
|
const weightsArray: OverlapInfo[] = [];
|
|
387
|
-
const markersArray:
|
|
461
|
+
const markersArray: Array<ScrollMarkerModel & {
|
|
462
|
+
element?: HTMLElement | null,
|
|
463
|
+
timeline?: ViewTimeline,
|
|
464
|
+
}> = [];
|
|
388
465
|
|
|
389
466
|
type OverlapInfo = {
|
|
467
|
+
name: string,
|
|
390
468
|
/** Marker time */
|
|
391
469
|
time: number,
|
|
392
470
|
/** Overlap in pixels */
|
|
393
471
|
weight: number,
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
// type SelectorCache = {
|
|
476
|
+
// /** The selector used to query the *elements */
|
|
477
|
+
// selector: string,
|
|
478
|
+
// elements: Element[] | null,
|
|
479
|
+
// usedElementCount: number,
|
|
480
|
+
// }
|
|
481
|
+
// const querySelectorResults: Array<SelectorCache> = [];
|
|
482
|
+
|
|
483
|
+
const needleScrollMarkerIndexCache = new Map<number, Element | null>();
|
|
484
|
+
const needleScrollMarkerNameCache = new Map<string, Element | null>();
|
|
485
|
+
let needsScrollMarkerRefresh = true;
|
|
486
|
+
|
|
487
|
+
function tryGetElementsForSelector(index: number, name: string, _cycle: number = 0): Element | null {
|
|
488
|
+
|
|
489
|
+
if (!needsScrollMarkerRefresh) {
|
|
490
|
+
if (name?.length) {
|
|
491
|
+
const element = needleScrollMarkerNameCache.get(name) || null;
|
|
492
|
+
if (element) return element;
|
|
493
|
+
// const isNumber = !isNaN(Number(name));
|
|
494
|
+
// if (!isNumber) {
|
|
495
|
+
// }
|
|
496
|
+
}
|
|
497
|
+
const element = needleScrollMarkerIndexCache.get(index) || null;
|
|
498
|
+
const value = element?.getAttribute("data-timeline-marker");
|
|
499
|
+
// if (value?.length) {
|
|
500
|
+
// if (cycle === 0) {
|
|
501
|
+
// // if the HTML marker we found by index does define a different marker name we try to find the correct HTML element by name
|
|
502
|
+
// return tryGetElementsForSelector(index, value, 1);
|
|
503
|
+
// }
|
|
504
|
+
// if (isDevEnvironment()) console.warn(`ScrollMarker name mismatch: expected "${name}", got "${value}"`);
|
|
505
|
+
// }
|
|
506
|
+
return element;
|
|
507
|
+
}
|
|
508
|
+
needsScrollMarkerRefresh = false;
|
|
509
|
+
needleScrollMarkerIndexCache.clear();
|
|
510
|
+
const markers = document.querySelectorAll(`[data-timeline-marker]`);
|
|
511
|
+
markers.forEach((m, i) => {
|
|
512
|
+
needleScrollMarkerIndexCache.set(i, m);
|
|
513
|
+
const name = m.getAttribute("data-timeline-marker");
|
|
514
|
+
if (name?.length) needleScrollMarkerNameCache.set(name, m);
|
|
515
|
+
});
|
|
516
|
+
needsScrollMarkerRefresh = false;
|
|
517
|
+
return tryGetElementsForSelector(index, name);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
// #region ScrollTimeline
|
|
522
|
+
|
|
523
|
+
function calculateTimelinePositionNormalized(timeline: ViewTimeline) {
|
|
524
|
+
if (!timeline.source) return 0;
|
|
525
|
+
const currentTime = timeline.currentTime;
|
|
526
|
+
const duration = timeline.duration;
|
|
527
|
+
let durationValue = 1;
|
|
528
|
+
if (duration.unit === "seconds") {
|
|
529
|
+
durationValue = duration.value;
|
|
530
|
+
}
|
|
531
|
+
else if (duration.unit === "percent") {
|
|
532
|
+
durationValue = duration.value;
|
|
533
|
+
}
|
|
534
|
+
const t01 = currentTime.unit === "seconds" ? (currentTime.value / durationValue) : (currentTime.value / 100);
|
|
535
|
+
return t01;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
declare global {
|
|
540
|
+
interface ViewTimeline {
|
|
541
|
+
axis: 'block' | 'inline';
|
|
542
|
+
currentTime: { unit: 'seconds' | 'percent', value: number };
|
|
543
|
+
duration: { unit: 'seconds' | 'percent', value: number };
|
|
544
|
+
source: Element | null;
|
|
545
|
+
startOffset: { unit: 'px', value: number };
|
|
546
|
+
endOffset: { unit: 'px', value: number };
|
|
547
|
+
}
|
|
394
548
|
}
|