@needle-tools/engine 4.10.0-next.4f9d92a → 4.10.0-next.870425c
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 +8 -3
- package/README.md +2 -1
- package/dist/{needle-engine.bundle-D4dO0t5I.umd.cjs → needle-engine.bundle-0b6rexDr.umd.cjs} +139 -137
- package/dist/{needle-engine.bundle-BeZ_xmJa.js → needle-engine.bundle-B5GtGvbq.js} +7750 -7653
- package/dist/needle-engine.bundle-CicGQeCY.min.js +1652 -0
- package/dist/needle-engine.js +15 -14
- 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/engine_camera.js +5 -5
- package/lib/engine/engine_camera.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/extension_utils.js +1 -1
- package/lib/engine/extensions/extension_utils.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/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/Camera.d.ts +1 -1
- package/lib/engine-components/Camera.js +1 -1
- package/lib/engine-components/CameraUtils.js +2 -1
- package/lib/engine-components/CameraUtils.js.map +1 -1
- package/lib/engine-components/CharacterController.d.ts +2 -2
- package/lib/engine-components/CharacterController.js +2 -2
- package/lib/engine-components/OrbitControls.d.ts +1 -1
- package/lib/engine-components/OrbitControls.js +1 -1
- package/lib/engine-components/Skybox.js +22 -4
- package/lib/engine-components/Skybox.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.js +1 -1
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/TimelineModels.d.ts +37 -2
- package/lib/engine-components/timeline/TimelineModels.js +6 -0
- package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +2 -1
- package/lib/engine-components/timeline/TimelineTracks.js +26 -23
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/web/ScrollFollow.d.ts +2 -0
- package/lib/engine-components/web/ScrollFollow.js +114 -99
- package/lib/engine-components/web/ScrollFollow.js.map +1 -1
- package/lib/engine-components/web/ViewBox.d.ts +33 -3
- package/lib/engine-components/web/ViewBox.js +133 -49
- package/lib/engine-components/web/ViewBox.js.map +1 -1
- package/lib/engine-components/webxr/WebARSessionRoot.js +1 -0
- package/lib/engine-components/webxr/WebARSessionRoot.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 +2 -1
- package/src/engine/engine_camera.ts +5 -7
- 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/extension_utils.ts +1 -1
- package/src/engine/webcomponents/needle-engine.ts +33 -6
- package/src/engine/xr/NeedleXRController.ts +36 -4
- package/src/engine-components/Camera.ts +1 -1
- package/src/engine-components/CameraUtils.ts +1 -1
- package/src/engine-components/CharacterController.ts +2 -2
- package/src/engine-components/OrbitControls.ts +1 -1
- package/src/engine-components/Skybox.ts +26 -7
- package/src/engine-components/debug/LogStats.ts +1 -0
- package/src/engine-components/timeline/PlayableDirector.ts +1 -1
- package/src/engine-components/timeline/TimelineModels.ts +37 -3
- package/src/engine-components/timeline/TimelineTracks.ts +26 -23
- package/src/engine-components/web/ScrollFollow.ts +121 -103
- package/src/engine-components/web/ViewBox.ts +140 -50
- package/src/engine-components/webxr/WebARSessionRoot.ts +1 -0
- package/src/engine-components-experimental/Presentation.ts +1 -0
- package/dist/needle-engine.bundle-C3bpSNYu.min.js +0 -1650
- package/dist/vendor-D0Yvltn9.umd.cjs +0 -1121
- package/dist/vendor-DU8tJyl_.js +0 -14366
- package/dist/vendor-JyrX4DVM.min.js +0 -1121
|
@@ -8,7 +8,7 @@ import { syncField } from "../engine/engine_networking_auto.js";
|
|
|
8
8
|
import { loadPMREM } from "../engine/engine_pmrem.js";
|
|
9
9
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
10
10
|
import { type IContext } from "../engine/engine_types.js";
|
|
11
|
-
import { addAttributeChangeCallback, getParam, PromiseAllWithErrors, removeAttributeChangeCallback } from "../engine/engine_utils.js";
|
|
11
|
+
import { addAttributeChangeCallback, getParam, PromiseAllWithErrors, removeAttributeChangeCallback, toSourceId } from "../engine/engine_utils.js";
|
|
12
12
|
import { registerObservableAttribute } from "../engine/webcomponents/needle-engine.extras.js";
|
|
13
13
|
import { Camera, ClearFlags } from "./Camera.js";
|
|
14
14
|
import { Behaviour, GameObject } from "./Component.js";
|
|
@@ -27,15 +27,34 @@ function createRemoteSkyboxComponent(context: IContext, url: string, skybox: boo
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const remote = new RemoteSkybox();
|
|
30
|
+
remote.sourceId = toSourceId(url);
|
|
30
31
|
remote.allowDrop = false;
|
|
31
32
|
remote.allowNetworking = false;
|
|
32
33
|
remote.background = skybox;
|
|
33
34
|
remote.environment = environment;
|
|
34
35
|
GameObject.addComponent(context.scene, remote);
|
|
35
36
|
const urlChanged = newValue => {
|
|
36
|
-
if (
|
|
37
|
-
if (
|
|
38
|
-
|
|
37
|
+
if (debug) console.log(attribute, "CHANGED TO", newValue);
|
|
38
|
+
if (newValue) {
|
|
39
|
+
if (typeof newValue !== "string") {
|
|
40
|
+
console.warn("Invalid attribute value for " + attribute);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
remote.setSkybox(newValue);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
if (remote.sourceId) {
|
|
47
|
+
if (environment) {
|
|
48
|
+
if (!context.sceneLighting.internalEnableReflection(remote.sourceId)) {
|
|
49
|
+
context.scene.environment = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (skybox) {
|
|
53
|
+
const skybox = context.lightmaps.tryGetSkybox(remote.sourceId);
|
|
54
|
+
context.scene.background = skybox;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
39
58
|
};
|
|
40
59
|
addAttributeChangeCallback(context.domElement, attribute, urlChanged);
|
|
41
60
|
remote.addEventListener("destroy", () => {
|
|
@@ -50,7 +69,7 @@ ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, (args) => {
|
|
|
50
69
|
const context = args.context;
|
|
51
70
|
const backgroundImage = context.domElement.getAttribute("background-image");
|
|
52
71
|
const environmentImage = context.domElement.getAttribute("environment-image");
|
|
53
|
-
|
|
72
|
+
|
|
54
73
|
if (backgroundImage) {
|
|
55
74
|
if (debug) console.log("Creating RemoteSkybox to load background " + backgroundImage);
|
|
56
75
|
// if the user is loading a GLB without a camera then the CameraUtils (which creates the default camera)
|
|
@@ -246,8 +265,8 @@ export class RemoteSkybox extends Behaviour {
|
|
|
246
265
|
envMap.needsUpdate = true;
|
|
247
266
|
}
|
|
248
267
|
|
|
249
|
-
if(this.destroyed) return;
|
|
250
|
-
if(!this.context) {
|
|
268
|
+
if (this.destroyed) return;
|
|
269
|
+
if (!this.context) {
|
|
251
270
|
console.warn("RemoteSkybox: Context is not available - can not apply skybox.");
|
|
252
271
|
return;
|
|
253
272
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { AnimationClip, Object3D, Quaternion, Vector3 } from "three";
|
|
2
|
-
import { Behavior } from "three-mesh-ui";
|
|
3
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @category Animation and Sequencing
|
|
5
|
+
*/
|
|
4
6
|
export declare type TimelineAssetModel = {
|
|
5
7
|
name: string;
|
|
6
8
|
tracks: TrackModel[];
|
|
7
9
|
}
|
|
8
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @category Animation and Sequencing
|
|
13
|
+
*/
|
|
9
14
|
export enum TrackType {
|
|
10
15
|
Activation = "ActivationTrack",
|
|
11
16
|
Animation = "AnimationTrack",
|
|
@@ -15,6 +20,9 @@ export enum TrackType {
|
|
|
15
20
|
Signal = "SignalTrack",
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @category Animation and Sequencing
|
|
25
|
+
*/
|
|
18
26
|
export enum ClipExtrapolation {
|
|
19
27
|
None = 0,
|
|
20
28
|
Hold = 1,
|
|
@@ -23,6 +31,9 @@ export enum ClipExtrapolation {
|
|
|
23
31
|
Continue = 4
|
|
24
32
|
};
|
|
25
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @category Animation and Sequencing
|
|
36
|
+
*/
|
|
26
37
|
export declare type TrackModel = {
|
|
27
38
|
name: string;
|
|
28
39
|
type: TrackType;
|
|
@@ -37,11 +48,17 @@ export declare type TrackModel = {
|
|
|
37
48
|
declare type Vec3 = { x: number, y: number, z: number };
|
|
38
49
|
declare type Quat = { x: number, y: number, z: number, w: number };
|
|
39
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @category Animation and Sequencing
|
|
53
|
+
*/
|
|
40
54
|
export declare type TrackOffset = {
|
|
41
55
|
position: Vec3 | Vector3;
|
|
42
56
|
rotation: Quat | Quaternion;
|
|
43
57
|
}
|
|
44
58
|
|
|
59
|
+
/**
|
|
60
|
+
* @category Animation and Sequencing
|
|
61
|
+
*/
|
|
45
62
|
export declare type ClipModel = {
|
|
46
63
|
start: number;
|
|
47
64
|
end: number;
|
|
@@ -56,6 +73,9 @@ export declare type ClipModel = {
|
|
|
56
73
|
reversed?: boolean;
|
|
57
74
|
}
|
|
58
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @category Animation and Sequencing
|
|
78
|
+
*/
|
|
59
79
|
export declare type AnimationClipModel = {
|
|
60
80
|
clip: string | number | AnimationClip;
|
|
61
81
|
loop: boolean;
|
|
@@ -65,12 +85,18 @@ export declare type AnimationClipModel = {
|
|
|
65
85
|
rotation?: Quat | Quaternion;
|
|
66
86
|
}
|
|
67
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @category Animation and Sequencing
|
|
90
|
+
*/
|
|
68
91
|
export declare type AudioClipModel = {
|
|
69
92
|
clip: string;
|
|
70
93
|
loop: boolean;
|
|
71
94
|
volume: number;
|
|
72
95
|
}
|
|
73
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @category Animation and Sequencing
|
|
99
|
+
*/
|
|
74
100
|
export declare type ControlClipModel = {
|
|
75
101
|
sourceObject: string | Object3D;
|
|
76
102
|
controlActivation: boolean;
|
|
@@ -81,11 +107,16 @@ export enum MarkerType {
|
|
|
81
107
|
Signal = "SignalEmitter",
|
|
82
108
|
}
|
|
83
109
|
|
|
84
|
-
|
|
110
|
+
/**
|
|
111
|
+
* @category Animation and Sequencing
|
|
112
|
+
*/export declare class MarkerModel {
|
|
85
113
|
type: MarkerType;
|
|
86
114
|
time: number;
|
|
87
115
|
}
|
|
88
116
|
|
|
117
|
+
/**
|
|
118
|
+
* @category Animation and Sequencing
|
|
119
|
+
*/
|
|
89
120
|
export declare class SignalMarkerModel extends MarkerModel {
|
|
90
121
|
retroActive: boolean;
|
|
91
122
|
emitOnce: boolean;
|
|
@@ -99,5 +130,8 @@ export declare class SignalMarkerModel extends MarkerModel {
|
|
|
99
130
|
* ```html
|
|
100
131
|
* <div data-timeline-marker>...</div>
|
|
101
132
|
* ```
|
|
102
|
-
|
|
133
|
+
*
|
|
134
|
+
* @link [Example Project using ScrollMarker](https://scrollytelling-bike-z23hmxb2gnu5a.needle.run/)
|
|
135
|
+
* @category Animation and Sequencing
|
|
136
|
+
*/
|
|
103
137
|
export type ScrollMarkerModel = MarkerModel & { name: string };
|
|
@@ -187,21 +187,22 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
187
187
|
return;
|
|
188
188
|
}
|
|
189
189
|
// we only want to hook into the binding of the root object
|
|
190
|
-
// TODO: test with a clip with multiple roots
|
|
191
|
-
const parts = clip.tracks[0].name.split(".");
|
|
192
|
-
const rootName = parts[parts.length - 2];
|
|
193
|
-
const positionTrackName = rootName + ".position";
|
|
194
|
-
const rotationTrackName = rootName + ".quaternion";
|
|
195
190
|
let foundPositionTrack: boolean = false;
|
|
196
191
|
let foundRotationTrack: boolean = false;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
}
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
208
|
|
|
@@ -226,16 +227,14 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
226
227
|
const trackName = baseName + ".position";
|
|
227
228
|
if (debug) console.warn("Create position track", objName, targetObj);
|
|
228
229
|
// apply initial local position so it doesnt get flipped or otherwise changed
|
|
229
|
-
const
|
|
230
|
-
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]);
|
|
231
231
|
clip.tracks.push(track);
|
|
232
232
|
this.createPositionInterpolant(clip, clipModel, track);
|
|
233
233
|
}
|
|
234
234
|
else if (!foundRotationTrack) {
|
|
235
235
|
const trackName = clip.tracks[0].name.substring(0, indexOfProperty) + ".quaternion";
|
|
236
236
|
if (debug) console.warn("Create quaternion track", objName, targetObj);
|
|
237
|
-
const
|
|
238
|
-
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]);
|
|
239
238
|
clip.tracks.push(track);
|
|
240
239
|
this.createRotationInterpolant(clip, clipModel, track);
|
|
241
240
|
}
|
|
@@ -834,23 +833,27 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
834
833
|
|
|
835
834
|
export class MarkerTrackHandler extends TrackHandler {
|
|
836
835
|
models: Array<Models.MarkerModel & Record<string, any>> = [];
|
|
837
|
-
|
|
836
|
+
needsSorting = true;
|
|
838
837
|
|
|
839
838
|
*foreachMarker<T>(type: string | null = null) {
|
|
839
|
+
if(this.needsSorting) this.sort();
|
|
840
840
|
for (const model of this.models) {
|
|
841
841
|
if (model && model.type === type) yield model as T;
|
|
842
842
|
}
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
onEnable() {
|
|
846
|
-
this.
|
|
846
|
+
this.needsSorting = true;
|
|
847
847
|
}
|
|
848
848
|
|
|
849
849
|
evaluate(_time: number) {
|
|
850
|
-
if (this.
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
+
|
|
854
857
|
}
|
|
855
858
|
}
|
|
856
859
|
|
|
@@ -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".
|
|
@@ -173,8 +176,8 @@ export class ScrollFollow extends Behaviour {
|
|
|
173
176
|
|
|
174
177
|
const value = this.invert ? 1 - this._current_value : this._current_value;
|
|
175
178
|
|
|
176
|
-
const height = this._rangeEndValue - this._rangeStartValue;
|
|
177
|
-
const pixelValue = this._rangeStartValue + value * height;
|
|
179
|
+
// const height = this._rangeEndValue - this._rangeStartValue;
|
|
180
|
+
// const pixelValue = this._rangeStartValue + value * height;
|
|
178
181
|
|
|
179
182
|
// apply scroll to target(s)
|
|
180
183
|
if (Array.isArray(this.target)) {
|
|
@@ -185,7 +188,7 @@ export class ScrollFollow extends Behaviour {
|
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
if (debug && this.context.time.frame % 30 === 0) {
|
|
188
|
-
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}]`);
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
194
|
}
|
|
@@ -303,22 +306,24 @@ export class ScrollFollow extends Behaviour {
|
|
|
303
306
|
const index = markerIndex++;
|
|
304
307
|
|
|
305
308
|
// Get marker elements from DOM
|
|
306
|
-
if ((marker.element === undefined || marker.needsUpdate === true || /** element is not in DOM anymore? */ (!marker.element?.parentNode))) {
|
|
309
|
+
if ((marker.element === undefined || marker.needsUpdate === true || /** element is not in DOM anymore? */ (marker.element && !marker.element?.parentNode))) {
|
|
307
310
|
marker.needsUpdate = false;
|
|
308
311
|
try {
|
|
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
|
|
309
313
|
marker.element = tryGetElementsForSelector(index, marker.name) as HTMLElement | null;
|
|
310
|
-
if (debug) console.debug(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
+
}
|
|
322
327
|
}
|
|
323
328
|
catch (error) {
|
|
324
329
|
marker.element = null;
|
|
@@ -349,57 +354,75 @@ export class ScrollFollow extends Behaviour {
|
|
|
349
354
|
|
|
350
355
|
weightsArray.length = 0;
|
|
351
356
|
let sum = 0;
|
|
357
|
+
const oneFrameTime = 1 / 60;
|
|
352
358
|
|
|
353
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
|
|
354
360
|
let markerCount = 0;
|
|
355
|
-
for (
|
|
356
|
-
|
|
361
|
+
for (let i = 0; i < markersArray.length; i++) {
|
|
362
|
+
const marker = markersArray[i];
|
|
357
363
|
if (!marker.element) continue;
|
|
364
|
+
const nextMarker = markersArray[i + 1];
|
|
365
|
+
|
|
366
|
+
const nextTime = nextMarker
|
|
367
|
+
? (nextMarker.time - oneFrameTime)
|
|
368
|
+
: duration;
|
|
358
369
|
|
|
359
370
|
markerCount += 1;
|
|
360
371
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
+
}
|
|
393
|
+
}
|
|
394
|
+
continue;
|
|
373
395
|
// if(this.context.time.frame % 10 === 0) console.log(marker.element?.className, timeline, calculateTimelinePositionNormalized(timeline!));
|
|
374
396
|
|
|
375
|
-
const top = marker.element.offsetTop;
|
|
376
|
-
const height = marker.element.offsetHeight;
|
|
377
|
-
const bottom = top + height;
|
|
378
|
-
let overlap = 0;
|
|
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;
|
|
379
401
|
|
|
380
|
-
// 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
|
|
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
|
|
381
403
|
|
|
382
|
-
if (bottom < currentTop) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
else if (top > currentBottom) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
+
// }
|
|
396
419
|
|
|
397
|
-
|
|
420
|
+
// // if(this.context.time.frame % 20 === 0) console.log(overlap)
|
|
398
421
|
|
|
399
|
-
if (overlap > 0) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
422
|
+
// if (overlap > 0) {
|
|
423
|
+
// weightsArray.push({ time: marker.time, weight: overlap });
|
|
424
|
+
// sum += overlap;
|
|
425
|
+
// }
|
|
403
426
|
}
|
|
404
427
|
|
|
405
428
|
if (weightsArray.length <= 0 && markerCount <= 0) {
|
|
@@ -407,19 +430,26 @@ export class ScrollFollow extends Behaviour {
|
|
|
407
430
|
}
|
|
408
431
|
else if (weightsArray.length > 0) {
|
|
409
432
|
// normalize and calculate weighted time
|
|
410
|
-
let time = 0;
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
+
}
|
|
417
442
|
}
|
|
418
|
-
|
|
419
|
-
if (this.damping <= 0)
|
|
443
|
+
if (this.damping <= 0) {
|
|
420
444
|
director.time = time;
|
|
421
|
-
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
422
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(", "));
|
|
452
|
+
}
|
|
423
453
|
}
|
|
424
454
|
}
|
|
425
455
|
|
|
@@ -428,9 +458,13 @@ export class ScrollFollow extends Behaviour {
|
|
|
428
458
|
|
|
429
459
|
|
|
430
460
|
const weightsArray: OverlapInfo[] = [];
|
|
431
|
-
const markersArray:
|
|
461
|
+
const markersArray: Array<ScrollMarkerModel & {
|
|
462
|
+
element?: HTMLElement | null,
|
|
463
|
+
timeline?: ViewTimeline,
|
|
464
|
+
}> = [];
|
|
432
465
|
|
|
433
466
|
type OverlapInfo = {
|
|
467
|
+
name: string,
|
|
434
468
|
/** Marker time */
|
|
435
469
|
time: number,
|
|
436
470
|
/** Overlap in pixels */
|
|
@@ -446,17 +480,29 @@ type OverlapInfo = {
|
|
|
446
480
|
// }
|
|
447
481
|
// const querySelectorResults: Array<SelectorCache> = [];
|
|
448
482
|
|
|
449
|
-
const needleScrollMarkerCacheKey = "data-timeline-marker";
|
|
450
483
|
const needleScrollMarkerIndexCache = new Map<number, Element | null>();
|
|
451
484
|
const needleScrollMarkerNameCache = new Map<string, Element | null>();
|
|
452
485
|
let needsScrollMarkerRefresh = true;
|
|
453
486
|
|
|
454
|
-
function tryGetElementsForSelector(index: number, name: string): Element | null {
|
|
487
|
+
function tryGetElementsForSelector(index: number, name: string, _cycle: number = 0): Element | null {
|
|
455
488
|
|
|
456
489
|
if (!needsScrollMarkerRefresh) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
+
// }
|
|
460
506
|
return element;
|
|
461
507
|
}
|
|
462
508
|
needsScrollMarkerRefresh = false;
|
|
@@ -467,26 +513,8 @@ function tryGetElementsForSelector(index: number, name: string): Element | null
|
|
|
467
513
|
const name = m.getAttribute("data-timeline-marker");
|
|
468
514
|
if (name?.length) needleScrollMarkerNameCache.set(name, m);
|
|
469
515
|
});
|
|
470
|
-
|
|
471
|
-
return
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
/* e.g.
|
|
475
|
-
<div class="section behind start" data-needle-scroll-marker>
|
|
476
|
-
*/
|
|
477
|
-
// console.log(index, element)
|
|
478
|
-
if (element) return element;
|
|
479
|
-
|
|
480
|
-
// for (const entry of querySelectorResults) {
|
|
481
|
-
// if (entry.selector === selector) {
|
|
482
|
-
// const index = entry.usedElementCount++;
|
|
483
|
-
// return entry.elements && index < entry.elements.length ? entry.elements[index] : null;
|
|
484
|
-
// }
|
|
485
|
-
// }
|
|
486
|
-
// const elements = document.querySelectorAll(selector);
|
|
487
|
-
// querySelectorResults.push({ selector, elements: Array.from(elements), usedElementCount: 1 });
|
|
488
|
-
// if (elements.length > 0) return elements[0];
|
|
489
|
-
return null;
|
|
516
|
+
needsScrollMarkerRefresh = false;
|
|
517
|
+
return tryGetElementsForSelector(index, name);
|
|
490
518
|
}
|
|
491
519
|
|
|
492
520
|
|
|
@@ -506,16 +534,6 @@ function calculateTimelinePositionNormalized(timeline: ViewTimeline) {
|
|
|
506
534
|
const t01 = currentTime.unit === "seconds" ? (currentTime.value / durationValue) : (currentTime.value / 100);
|
|
507
535
|
return t01;
|
|
508
536
|
}
|
|
509
|
-
function calculateNormalizedOverlap(timeline: ViewTimeline) {
|
|
510
|
-
if (!timeline.source) return 0;
|
|
511
|
-
const start = timeline.startOffset;
|
|
512
|
-
const end = timeline.endOffset;
|
|
513
|
-
const total = start.value + end.value;
|
|
514
|
-
if (total <= 0) return 1;
|
|
515
|
-
const startNorm = start.value / total;
|
|
516
|
-
const endNorm = end.value / total;
|
|
517
|
-
return 1 - (startNorm + endNorm);
|
|
518
|
-
}
|
|
519
537
|
|
|
520
538
|
|
|
521
539
|
declare global {
|