rayzee 8.8.2 → 9.0.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.
@@ -0,0 +1,213 @@
1
+ import { InterpolateSmooth, MathUtils, NumberKeyframeTrack, Vector3, VectorKeyframeTrack } from 'three';
2
+
3
+ /**
4
+ * @typedef {Object} CameraPose
5
+ * @property {Vector3} position
6
+ * @property {Vector3} target - what the camera looks at
7
+ * @property {number} fov - vertical, degrees
8
+ * @property {number} orthoHeight - the view's height while orthographic, world units
9
+ */
10
+
11
+ /** @typedef {CameraPose & { id: number, time: number }} CameraKey */
12
+
13
+ // Keys closer than this in time are spread to it: an interval of zero divides by zero.
14
+ const MIN_KEY_GAP = 1e-3;
15
+ // Where a new key lands after the last one.
16
+ const KEY_SPACING_SECONDS = 2;
17
+
18
+ const smooth = ( Track, name, times, values ) => new Track( name, times, values, InterpolateSmooth ).createInterpolant();
19
+
20
+ /**
21
+ * The camera's keyframes: each a view at a time, in seconds. Between keys the camera glides along a
22
+ * curve through their positions while looking along one through their targets; outside them it holds.
23
+ * The orthographic height blends in log space, so a zoom reads as steady.
24
+ */
25
+ export class CameraTrack {
26
+
27
+ /**
28
+ * @param {import('../CameraManager.js').CameraManager} cameraManager - where views come from and go
29
+ * @param {Function} [onChange] - after keys are added, moved, changed or removed
30
+ */
31
+ constructor( cameraManager, onChange ) {
32
+
33
+ this._cameraManager = cameraManager;
34
+ this._onChange = onChange;
35
+
36
+ /** @type {CameraKey[]} sorted by time */
37
+ this.keys = [];
38
+ this._nextId = 1;
39
+ this._interpolants = null;
40
+
41
+ }
42
+
43
+ /** Two keys or more: one is only a still view. */
44
+ get animates() {
45
+
46
+ return this.keys.length > 1;
47
+
48
+ }
49
+
50
+ /** Seconds from 0 to the last key. */
51
+ get duration() {
52
+
53
+ return this.keys.length ? this.keys[ this.keys.length - 1 ].time : 0;
54
+
55
+ }
56
+
57
+ /**
58
+ * Keys the current view, KEY_SPACING_SECONDS after the last key unless given a time.
59
+ * @param {number} [time]
60
+ * @returns {CameraKey}
61
+ */
62
+ addKey( time = this.keys.length ? this.duration + KEY_SPACING_SECONDS : 0 ) {
63
+
64
+ return this.add( this._cameraManager.captureView(), time );
65
+
66
+ }
67
+
68
+ /** Gives a key the current view, at the same time. */
69
+ updateKey( id ) {
70
+
71
+ this.update( id, this._cameraManager.captureView() );
72
+
73
+ }
74
+
75
+ /**
76
+ * Puts the camera where the track has it at `time`.
77
+ * @returns {boolean} whether there was a key to go to
78
+ */
79
+ seek( time ) {
80
+
81
+ const pose = this.sample( time );
82
+ if ( pose ) this._cameraManager.applyPose( pose );
83
+ return !! pose;
84
+
85
+ }
86
+
87
+ /**
88
+ * @param {CameraPose} pose - copied
89
+ * @param {number} time
90
+ * @returns {CameraKey}
91
+ */
92
+ add( pose, time ) {
93
+
94
+ const key = { id: this._nextId ++, time, ...copyPose( pose ) };
95
+ this.keys.push( key );
96
+ this._changed();
97
+ return key;
98
+
99
+ }
100
+
101
+ /** Gives the key a new pose, at the same time. */
102
+ update( id, pose ) {
103
+
104
+ const key = this.get( id );
105
+ if ( ! key ) return;
106
+ Object.assign( key, copyPose( pose ) );
107
+ this._changed();
108
+
109
+ }
110
+
111
+ setTime( id, time ) {
112
+
113
+ const key = this.get( id );
114
+ if ( ! key || ! ( time >= 0 ) ) return;
115
+ key.time = time;
116
+ this._changed();
117
+
118
+ }
119
+
120
+ remove( id ) {
121
+
122
+ const count = this.keys.length;
123
+ this.keys = this.keys.filter( key => key.id !== id );
124
+ if ( this.keys.length !== count ) this._changed();
125
+
126
+ }
127
+
128
+ clear() {
129
+
130
+ if ( ! this.keys.length ) return;
131
+ this.keys = [];
132
+ this._changed();
133
+
134
+ }
135
+
136
+ dispose() {
137
+
138
+ this._cameraManager = this._onChange = null;
139
+
140
+ }
141
+
142
+ get( id ) {
143
+
144
+ return this.keys.find( key => key.id === id );
145
+
146
+ }
147
+
148
+ /**
149
+ * The pose at `time`, in seconds; null without keys.
150
+ * @param {number} time
151
+ * @param {CameraPose} [out]
152
+ * @returns {?CameraPose}
153
+ */
154
+ sample( time, out = { position: new Vector3(), target: new Vector3(), fov: 0, orthoHeight: 0 } ) {
155
+
156
+ if ( ! this.keys.length ) return null;
157
+
158
+ const { position, target, fov, logHeight, start, end } = this._interpolants ??= this._build();
159
+ const at = MathUtils.clamp( time, start, end );
160
+ out.position.fromArray( position.evaluate( at ) );
161
+ out.target.fromArray( target.evaluate( at ) );
162
+ out.fov = fov.evaluate( at )[ 0 ];
163
+ out.orthoHeight = Math.exp( logHeight.evaluate( at )[ 0 ] );
164
+ return out;
165
+
166
+ }
167
+
168
+ _build() {
169
+
170
+ const times = [];
171
+ for ( const key of this.keys ) times.push( times.length ? Math.max( key.time, times[ times.length - 1 ] + MIN_KEY_GAP ) : key.time );
172
+ const start = times[ 0 ];
173
+ const end = times[ times.length - 1 ];
174
+
175
+ // Each end gets a key mirrored past it, so the curve leaves the first key and reaches the last at
176
+ // rest. three's ZeroSlopeEnding would flatten only the start.
177
+ let keys = this.keys;
178
+ if ( keys.length > 1 ) {
179
+
180
+ const n = keys.length;
181
+ times.unshift( 2 * start - times[ 1 ] );
182
+ times.push( 2 * end - times[ n - 1 ] );
183
+ keys = [ keys[ 1 ], ...keys, keys[ n - 2 ] ];
184
+
185
+ }
186
+
187
+ const vectors = pick => keys.flatMap( key => pick( key ).toArray() );
188
+ return {
189
+ start,
190
+ end,
191
+ position: smooth( VectorKeyframeTrack, '.position', times, vectors( key => key.position ) ),
192
+ target: smooth( VectorKeyframeTrack, '.target', times, vectors( key => key.target ) ),
193
+ fov: smooth( NumberKeyframeTrack, '.fov', times, keys.map( key => key.fov ) ),
194
+ logHeight: smooth( NumberKeyframeTrack, '.orthoHeight', times, keys.map( key => Math.log( key.orthoHeight ) ) ),
195
+ };
196
+
197
+ }
198
+
199
+ _changed() {
200
+
201
+ this.keys.sort( ( a, b ) => a.time - b.time );
202
+ this._interpolants = null;
203
+ this._onChange?.();
204
+
205
+ }
206
+
207
+ }
208
+
209
+ function copyPose( { position, target, fov, orthoHeight } ) {
210
+
211
+ return { position: position.clone(), target: target.clone(), fov, orthoHeight };
212
+
213
+ }
@@ -0,0 +1,132 @@
1
+ import { EventDispatcher, MathUtils, Timer } from 'three';
2
+ import { EngineEvents } from '../../EngineEvents.js';
3
+ import { CameraTrack } from './CameraTrack.js';
4
+
5
+ /**
6
+ * The authored animation: keyframed tracks on one time axis, in seconds, of which the camera's
7
+ * ({@link CameraTrack}) is the first. A model's own clips stay with the AnimationManager.
8
+ */
9
+ export class TimelineManager extends EventDispatcher {
10
+
11
+ /**
12
+ * @param {Object} deps
13
+ * @param {import('../CameraManager.js').CameraManager} deps.cameraManager
14
+ * @param {Function} deps.onReset - restarts accumulation and wakes the frame loop
15
+ */
16
+ constructor( { cameraManager, onReset } ) {
17
+
18
+ super();
19
+
20
+ this._cameraManager = cameraManager;
21
+ this._onReset = onReset;
22
+
23
+ this.camera = new CameraTrack( cameraManager, () => this._changed( 'camera' ) );
24
+
25
+ this._playback = null;
26
+ this._timer = new Timer();
27
+ // A hidden page's time is not played.
28
+ if ( typeof document !== 'undefined' ) this._timer.connect( document );
29
+
30
+ }
31
+
32
+ /** Seconds to the last key of any track. */
33
+ get duration() {
34
+
35
+ return this.camera.duration;
36
+
37
+ }
38
+
39
+ get animates() {
40
+
41
+ return this.camera.animates;
42
+
43
+ }
44
+
45
+ /**
46
+ * Puts the scene where the timeline has it at `time`.
47
+ * @param {number} time - seconds
48
+ * @returns {boolean} whether anything moved
49
+ */
50
+ seek( time ) {
51
+
52
+ return this.camera.seek( time );
53
+
54
+ }
55
+
56
+ /**
57
+ * Runs the timeline in the viewport from the start, with the camera controls locked. Resolves when
58
+ * it ends or {@link stop} is called; the scene stays where it got to.
59
+ * @returns {Promise<void>}
60
+ */
61
+ play() {
62
+
63
+ this.stop();
64
+ return new Promise( resolve => {
65
+
66
+ const controls = this._cameraManager.controls;
67
+ this._playback = { elapsed: 0, resolve, controlsEnabled: controls.enabled };
68
+ controls.enabled = false;
69
+ this._timer.reset();
70
+ this._changed();
71
+ this._onReset?.();
72
+
73
+ } );
74
+
75
+ }
76
+
77
+ stop() {
78
+
79
+ const playback = this._playback;
80
+ if ( ! playback ) return;
81
+ this._playback = null;
82
+ this._cameraManager.controls.enabled = playback.controlsEnabled;
83
+ this._changed();
84
+ playback.resolve();
85
+
86
+ }
87
+
88
+ get isPlaying() {
89
+
90
+ return !! this._playback;
91
+
92
+ }
93
+
94
+ /** Per frame, before the camera controls update. */
95
+ update() {
96
+
97
+ const playback = this._playback;
98
+ if ( ! playback ) return;
99
+
100
+ this._timer.update();
101
+ playback.elapsed += this._timer.getDelta();
102
+ const time = MathUtils.clamp( playback.elapsed, 0, this.duration );
103
+ this.seek( time );
104
+ if ( time >= this.duration ) this.stop();
105
+
106
+ }
107
+
108
+ /** Drops every key: they belonged to the scene being replaced. */
109
+ clear() {
110
+
111
+ this.stop();
112
+ this.camera.clear();
113
+
114
+ }
115
+
116
+ dispose() {
117
+
118
+ this.stop();
119
+ this._timer.dispose();
120
+ this.camera.dispose();
121
+ this._cameraManager = this._onReset = null;
122
+
123
+ }
124
+
125
+ /** @param {string} [track] - whose keys changed; none for playback */
126
+ _changed( track ) {
127
+
128
+ this.dispatchEvent( { type: EngineEvents.TIMELINE_CHANGED, track } );
129
+
130
+ }
131
+
132
+ }