rayzee 8.8.2 → 9.1.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.
Files changed (104) hide show
  1. package/README.md +210 -20
  2. package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
  3. package/dist/{NeuralSuperRes-Dc6jRYke.js → NeuralSuperRes-CbNvQjCw.js} +5 -2
  4. package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
  5. package/dist/rayzee.es.js +10795 -5324
  6. package/dist/rayzee.umd.js +279 -268
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +4 -0
  9. package/src/EngineDefaults.js +60 -76
  10. package/src/EngineEvents.js +7 -0
  11. package/src/EngineIssues.js +6 -0
  12. package/src/Headless.js +3 -1
  13. package/src/Passes/AIUpscaler.js +2 -1
  14. package/src/Passes/OIDNDenoiser.js +3 -6
  15. package/src/Passes/OIDNTemporalHistory.js +25 -7
  16. package/src/PathTracerApp.js +313 -25
  17. package/src/Processor/ArchiveCache.js +120 -0
  18. package/src/Processor/ArchiveReader.js +179 -32
  19. package/src/Processor/AssetLoader.js +523 -76
  20. package/src/Processor/AtmosphereModel.js +465 -0
  21. package/src/Processor/BVHBuilder.js +4 -1
  22. package/src/Processor/BVHLeafFold.js +100 -0
  23. package/src/Processor/BVHRefitter.js +98 -214
  24. package/src/Processor/ChunkedRecords.js +189 -4
  25. package/src/Processor/EmissiveTriangleBuilder.js +8 -0
  26. package/src/Processor/GeometryExtractor.js +238 -40
  27. package/src/Processor/InstanceTable.js +55 -37
  28. package/src/Processor/PBRT/PBRTCurves.js +104 -27
  29. package/src/Processor/PBRT/PBRTParser.js +132 -22
  30. package/src/Processor/PBRT/PBRTSceneBuilder.js +210 -55
  31. package/src/Processor/PBRT/buffers.js +27 -0
  32. package/src/Processor/PBRT/index.js +55 -15
  33. package/src/Processor/PackedRayBuffer.js +10 -13
  34. package/src/Processor/ParallelBVHBuilder.js +36 -15
  35. package/src/Processor/PhysicalSky.js +308 -0
  36. package/src/Processor/QueueManager.js +1 -0
  37. package/src/Processor/SceneProcessor.js +1132 -181
  38. package/src/Processor/SplitBLAS.js +249 -0
  39. package/src/Processor/SunPosition.js +41 -0
  40. package/src/Processor/TextureCreator.js +21 -0
  41. package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
  42. package/src/Processor/Workers/BVHWorker.js +17 -13
  43. package/src/Processor/ZipReader.js +207 -0
  44. package/src/RenderSettings.js +49 -8
  45. package/src/SceneState/SceneState.js +367 -0
  46. package/src/SceneState/portable.js +52 -0
  47. package/src/Stages/MotionVector.js +8 -42
  48. package/src/Stages/NRD.js +30 -21
  49. package/src/Stages/NormalDepth.js +12 -23
  50. package/src/Stages/PathTracer.js +114 -6
  51. package/src/Stages/PathTracerStage.js +251 -104
  52. package/src/Storage/BLASCache.js +190 -0
  53. package/src/Storage/CDFCache.js +77 -0
  54. package/src/Storage/DownloadCache.js +266 -0
  55. package/src/Storage/GeometrySpill.js +217 -0
  56. package/src/Storage/SceneGraphCodec.js +503 -0
  57. package/src/Storage/SpillStore.js +112 -0
  58. package/src/Storage/StorageManager.js +934 -0
  59. package/src/Storage/StorageOps.js +137 -0
  60. package/src/Storage/StorageWorker.js +37 -0
  61. package/src/Storage/events.js +29 -0
  62. package/src/Storage/identity.js +77 -0
  63. package/src/Storage/index.js +9 -0
  64. package/src/Storage/inlineTransport.js +24 -0
  65. package/src/Storage/locks.js +99 -0
  66. package/src/Storage/openStorage.js +87 -0
  67. package/src/Storage/sceneCachePolicy.js +12 -0
  68. package/src/Storage/shared.js +9 -0
  69. package/src/Storage/transport.js +62 -0
  70. package/src/TSL/Atmosphere.js +684 -0
  71. package/src/TSL/BVHTraversal.js +288 -239
  72. package/src/TSL/CameraRay.js +99 -14
  73. package/src/TSL/Common.js +11 -2
  74. package/src/TSL/DebugKernel.js +2 -2
  75. package/src/TSL/Displacement.js +6 -6
  76. package/src/TSL/EmissiveSampling.js +5 -5
  77. package/src/TSL/EnvironmentCDF.js +176 -0
  78. package/src/TSL/GenerateKernel.js +5 -5
  79. package/src/TSL/HitFacet.js +4 -4
  80. package/src/TSL/LightsDirect.js +8 -8
  81. package/src/TSL/LightsSampling.js +55 -37
  82. package/src/TSL/Random.js +1 -0
  83. package/src/TSL/ShadeKernel.js +75 -19
  84. package/src/TSL/Sun.js +40 -0
  85. package/src/TSL/TextureSampling.js +6 -7
  86. package/src/index.js +22 -0
  87. package/src/managers/CameraManager.js +412 -56
  88. package/src/managers/DenoisingManager.js +4 -3
  89. package/src/managers/EnvironmentManager.js +263 -33
  90. package/src/managers/GoboManager.js +19 -8
  91. package/src/managers/IESManager.js +3 -3
  92. package/src/managers/InteractionManager.js +30 -2
  93. package/src/managers/LightManager.js +98 -5
  94. package/src/managers/MaterialDataManager.js +22 -2
  95. package/src/managers/TransformManager.js +26 -0
  96. package/src/managers/UniformManager.js +11 -7
  97. package/src/managers/VideoRenderManager.js +38 -17
  98. package/src/managers/ViewCamera.js +77 -0
  99. package/src/managers/WalkControls.js +262 -0
  100. package/src/managers/helpers/OutlineHelper.js +22 -2
  101. package/src/managers/timeline/CameraTrack.js +235 -0
  102. package/src/managers/timeline/TimelineManager.js +146 -0
  103. package/src/version.js +3 -0
  104. package/src/Processor/ProceduralSky.js +0 -291
@@ -0,0 +1,262 @@
1
+ import { Controls, MathUtils, Quaternion, Vector3 } from 'three';
2
+
3
+ const CROSSING_SECONDS = 8;
4
+ const FAST = 4;
5
+ const SLOW = 0.25;
6
+ const MAX_PITCH = MathUtils.degToRad( 89 );
7
+ const MAX_STEP_SECONDS = 0.25;
8
+
9
+ // Right, forward, up.
10
+ const KEY_DIRECTIONS = {
11
+ KeyD: [ 1, 0, 0 ], ArrowRight: [ 1, 0, 0 ],
12
+ KeyA: [ - 1, 0, 0 ], ArrowLeft: [ - 1, 0, 0 ],
13
+ KeyW: [ 0, 1, 0 ], ArrowUp: [ 0, 1, 0 ],
14
+ KeyS: [ 0, - 1, 0 ], ArrowDown: [ 0, - 1, 0 ],
15
+ KeyE: [ 0, 0, 1 ],
16
+ KeyQ: [ 0, 0, - 1 ],
17
+ };
18
+
19
+ // Focus that types, or opens a list, keeps its keys. A widget that handled a key prevents its default.
20
+ const TYPES_KEYS = 'input, textarea, select, [contenteditable]:not([contenteditable="false"]), '
21
+ + '[role=textbox], [role=combobox], [role=listbox], [role=menu], [role=menuitem]';
22
+
23
+ const speedFactor = event => event.shiftKey ? FAST : event.altKey ? SLOW : 1;
24
+
25
+ const _changeEvent = { type: 'change' };
26
+ const _startEvent = { type: 'start' };
27
+ const _forward = new Vector3();
28
+ const _right = new Vector3();
29
+ const _move = new Vector3();
30
+ const _axis = new Vector3();
31
+ const _q = new Quaternion();
32
+
33
+ /**
34
+ * First-person navigation: drag to look, W A S D or the arrows to walk level, E and Q to rise
35
+ * and sink, Shift faster, Alt slower.
36
+ *
37
+ * Rides on the orbit controls: moves only while they are enabled, and keeps their target ahead
38
+ * of the camera, since they re-aim the camera at it every frame.
39
+ */
40
+ export class WalkControls extends Controls {
41
+
42
+ /**
43
+ * @param {import('three').PerspectiveCamera} camera
44
+ * @param {HTMLElement} domElement
45
+ * @param {import('three/addons/controls/OrbitControls.js').OrbitControls} orbit
46
+ */
47
+ constructor( camera, domElement, orbit ) {
48
+
49
+ super( camera, domElement );
50
+
51
+ this.orbit = orbit;
52
+ this.enabled = false;
53
+ /** Scene units per second. */
54
+ this.speed = 1 / CROSSING_SECONDS;
55
+
56
+ this._held = new Set();
57
+ this._factor = 1;
58
+ this._lastTime = 0;
59
+ this._drag = null;
60
+
61
+ this._onKeyDown = this._onKeyDown.bind( this );
62
+ this._onKeyUp = this._onKeyUp.bind( this );
63
+ this.release = this.release.bind( this );
64
+ this._onPointerDown = this._onPointerDown.bind( this );
65
+ this._onPointerMove = this._onPointerMove.bind( this );
66
+ this._onPointerUp = this._onPointerUp.bind( this );
67
+
68
+ if ( domElement ) this.connect( domElement );
69
+
70
+ }
71
+
72
+ get active() {
73
+
74
+ return this.enabled && this.orbit.enabled;
75
+
76
+ }
77
+
78
+ /** Cross a scene this long in about eight seconds. */
79
+ fitSpeed( sceneSize ) {
80
+
81
+ this.speed = ( sceneSize > 0 ? sceneSize : 1 ) / CROSSING_SECONDS;
82
+
83
+ }
84
+
85
+ connect( element ) {
86
+
87
+ super.connect( element );
88
+ const doc = element.ownerDocument;
89
+ doc?.addEventListener( 'keydown', this._onKeyDown );
90
+ doc?.addEventListener( 'keyup', this._onKeyUp );
91
+ doc?.addEventListener( 'visibilitychange', this.release );
92
+ doc?.defaultView?.addEventListener( 'blur', this.release );
93
+ element.addEventListener( 'pointerdown', this._onPointerDown );
94
+ element.addEventListener( 'pointermove', this._onPointerMove );
95
+ element.addEventListener( 'pointerup', this._onPointerUp );
96
+ element.addEventListener( 'pointercancel', this._onPointerUp );
97
+
98
+ }
99
+
100
+ disconnect() {
101
+
102
+ const element = this.domElement;
103
+ if ( ! element ) return;
104
+ const doc = element.ownerDocument;
105
+ doc?.removeEventListener( 'keydown', this._onKeyDown );
106
+ doc?.removeEventListener( 'keyup', this._onKeyUp );
107
+ doc?.removeEventListener( 'visibilitychange', this.release );
108
+ doc?.defaultView?.removeEventListener( 'blur', this.release );
109
+ element.removeEventListener( 'pointerdown', this._onPointerDown );
110
+ element.removeEventListener( 'pointermove', this._onPointerMove );
111
+ element.removeEventListener( 'pointerup', this._onPointerUp );
112
+ element.removeEventListener( 'pointercancel', this._onPointerUp );
113
+
114
+ }
115
+
116
+ dispose() {
117
+
118
+ this.disconnect();
119
+
120
+ }
121
+
122
+ update( delta = this._elapsed() ) {
123
+
124
+ if ( ! this._held.size || ! this.active ) return false;
125
+
126
+ let right = 0, forward = 0, up = 0;
127
+ for ( const code of this._held ) {
128
+
129
+ const [ r, f, u ] = KEY_DIRECTIONS[ code ];
130
+ right += r;
131
+ forward += f;
132
+ up += u;
133
+
134
+ }
135
+
136
+ if ( ! right && ! forward && ! up ) return false;
137
+
138
+ const camera = this.object;
139
+ // Level axes from the camera's right, which stays level however far it looks down.
140
+ _right.set( 1, 0, 0 ).applyQuaternion( camera.quaternion );
141
+ _forward.crossVectors( camera.up, _right ).normalize();
142
+ _right.crossVectors( _forward, camera.up );
143
+
144
+ _move.set( 0, 0, 0 )
145
+ .addScaledVector( _right, right )
146
+ .addScaledVector( _forward, forward )
147
+ .addScaledVector( camera.up, up )
148
+ .normalize()
149
+ .multiplyScalar( this.speed * this._factor * delta );
150
+ camera.position.add( _move );
151
+ this.orbit.target.add( _move );
152
+ camera.updateMatrixWorld();
153
+
154
+ this.dispatchEvent( _changeEvent );
155
+ return true;
156
+
157
+ }
158
+
159
+ _elapsed() {
160
+
161
+ const now = performance.now();
162
+ const delta = Math.min( ( now - this._lastTime ) / 1000, MAX_STEP_SECONDS );
163
+ this._lastTime = now;
164
+ return delta;
165
+
166
+ }
167
+
168
+ /** Turn by a drag of dx, dy pixels, so the scene follows the pointer. */
169
+ _look( dx, dy ) {
170
+
171
+ const camera = this.object;
172
+ const target = this.orbit.target;
173
+ const distance = camera.position.distanceTo( target ) || 1;
174
+ const radiansPerPixel = MathUtils.degToRad( camera.fov ) / ( this.domElement.clientHeight || 1 );
175
+
176
+ camera.quaternion.premultiply( _q.setFromAxisAngle( camera.up, dx * radiansPerPixel ) );
177
+
178
+ camera.getWorldDirection( _forward );
179
+ const pitch = Math.asin( MathUtils.clamp( _forward.dot( camera.up ), - 1, 1 ) );
180
+ const nextPitch = MathUtils.clamp( pitch + dy * radiansPerPixel, - MAX_PITCH, MAX_PITCH );
181
+ camera.quaternion.multiply( _q.setFromAxisAngle( _axis.set( 1, 0, 0 ), nextPitch - pitch ) );
182
+ camera.updateMatrixWorld();
183
+
184
+ target.copy( camera.position ).addScaledVector( camera.getWorldDirection( _forward ), distance );
185
+ this.dispatchEvent( _changeEvent );
186
+
187
+ }
188
+
189
+ _onKeyDown( event ) {
190
+
191
+ if ( ! this.active || event.defaultPrevented || event.target?.closest?.( TYPES_KEYS ) ) return;
192
+
193
+ // Browser shortcuts pass through, and macOS drops the key-up of a key pressed while Cmd is down.
194
+ if ( event.metaKey || event.ctrlKey ) {
195
+
196
+ this.release();
197
+ return;
198
+
199
+ }
200
+
201
+ this._factor = speedFactor( event );
202
+ if ( ! KEY_DIRECTIONS[ event.code ] ) return;
203
+
204
+ event.preventDefault();
205
+ if ( this._held.has( event.code ) ) return;
206
+ this._held.add( event.code );
207
+
208
+ // After the key is held: a woken loop runs its first frame inside this dispatch.
209
+ if ( this._held.size === 1 ) {
210
+
211
+ this._lastTime = performance.now();
212
+ this.dispatchEvent( _startEvent );
213
+
214
+ }
215
+
216
+ }
217
+
218
+ _onKeyUp( event ) {
219
+
220
+ this._factor = speedFactor( event );
221
+ this._held.delete( event.code );
222
+
223
+ }
224
+
225
+ /** Let go of every held key. */
226
+ release() {
227
+
228
+ this._held.clear();
229
+ this._factor = 1;
230
+
231
+ }
232
+
233
+ _onPointerDown( event ) {
234
+
235
+ if ( ! this.active || event.button !== 0 || ! event.isPrimary ) return;
236
+ this._drag = { id: event.pointerId, x: event.clientX, y: event.clientY };
237
+ this.domElement.setPointerCapture?.( event.pointerId );
238
+
239
+ }
240
+
241
+ _onPointerMove( event ) {
242
+
243
+ const drag = this._drag;
244
+ if ( ! drag || event.pointerId !== drag.id ) return;
245
+
246
+ const dx = event.clientX - drag.x;
247
+ const dy = event.clientY - drag.y;
248
+ drag.x = event.clientX;
249
+ drag.y = event.clientY;
250
+ if ( this.active && ( dx || dy ) ) this._look( dx, dy );
251
+
252
+ }
253
+
254
+ _onPointerUp( event ) {
255
+
256
+ if ( this._drag?.id !== event.pointerId ) return;
257
+ this._drag = null;
258
+ this.domElement.releasePointerCapture?.( event.pointerId );
259
+
260
+ }
261
+
262
+ }
@@ -28,12 +28,22 @@ export class OutlineHelper {
28
28
 
29
29
  this.layer = 'scene';
30
30
  this._enabled = true;
31
+ this._scene = scene;
32
+ this._camera = camera;
33
+ this._build( [] );
34
+
35
+ }
36
+
37
+ // OutlineNode reads the camera's type once, as it builds its depth test.
38
+ _build( selectedObjects ) {
39
+
40
+ this._orthographic = this._camera.isOrthographicCamera === true;
31
41
 
32
42
  // Outline node (handles its own multi-pass rendering via updateBefore).
33
43
  // It sizes its render targets from the drawing buffer of whatever renderer
34
44
  // draws it — the view overlay surface — so no size override is needed.
35
- this._outlineNode = outline( scene, camera, {
36
- selectedObjects: [],
45
+ this._outlineNode = outline( this._scene, this._camera, {
46
+ selectedObjects,
37
47
  edgeThickness: uniform( 1.0 ),
38
48
  edgeGlow: uniform( 0.0 ),
39
49
  } );
@@ -98,6 +108,16 @@ export class OutlineHelper {
98
108
 
99
109
  if ( ! this.visible ) return;
100
110
 
111
+ if ( this._camera.isOrthographicCamera !== this._orthographic ) {
112
+
113
+ // dispose() empties the list it was given.
114
+ const selected = [ ...this._outlineNode.selectedObjects ];
115
+ this._outlineNode.dispose();
116
+ this._material.dispose();
117
+ this._build( selected );
118
+
119
+ }
120
+
101
121
  const prevAutoClear = renderer.autoClear;
102
122
  renderer.autoClear = false;
103
123
  renderer.setRenderTarget( null );
@@ -0,0 +1,235 @@
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
+ /** The keys as plain data, for a saved session. */
137
+ serialize() {
138
+
139
+ return this.keys.map( ( { time, position, target, fov, orthoHeight } ) => ( { time, position: position.toArray(), target: target.toArray(), fov, orthoHeight } ) );
140
+
141
+ }
142
+
143
+ /** Replaces every key with the ones {@link serialize} described. */
144
+ restore( keys ) {
145
+
146
+ this.keys = ( keys ?? [] ).map( key => ( {
147
+ id: this._nextId ++,
148
+ time: key.time,
149
+ position: new Vector3().fromArray( key.position ),
150
+ target: new Vector3().fromArray( key.target ),
151
+ fov: key.fov,
152
+ orthoHeight: key.orthoHeight,
153
+ } ) );
154
+ this._changed();
155
+
156
+ }
157
+
158
+ dispose() {
159
+
160
+ this._cameraManager = this._onChange = null;
161
+
162
+ }
163
+
164
+ get( id ) {
165
+
166
+ return this.keys.find( key => key.id === id );
167
+
168
+ }
169
+
170
+ /**
171
+ * The pose at `time`, in seconds; null without keys.
172
+ * @param {number} time
173
+ * @param {CameraPose} [out]
174
+ * @returns {?CameraPose}
175
+ */
176
+ sample( time, out = { position: new Vector3(), target: new Vector3(), fov: 0, orthoHeight: 0 } ) {
177
+
178
+ if ( ! this.keys.length ) return null;
179
+
180
+ const { position, target, fov, logHeight, start, end } = this._interpolants ??= this._build();
181
+ const at = MathUtils.clamp( time, start, end );
182
+ out.position.fromArray( position.evaluate( at ) );
183
+ out.target.fromArray( target.evaluate( at ) );
184
+ out.fov = fov.evaluate( at )[ 0 ];
185
+ out.orthoHeight = Math.exp( logHeight.evaluate( at )[ 0 ] );
186
+ return out;
187
+
188
+ }
189
+
190
+ _build() {
191
+
192
+ const times = [];
193
+ for ( const key of this.keys ) times.push( times.length ? Math.max( key.time, times[ times.length - 1 ] + MIN_KEY_GAP ) : key.time );
194
+ const start = times[ 0 ];
195
+ const end = times[ times.length - 1 ];
196
+
197
+ // Each end gets a key mirrored past it, so the curve leaves the first key and reaches the last at
198
+ // rest. three's ZeroSlopeEnding would flatten only the start.
199
+ let keys = this.keys;
200
+ if ( keys.length > 1 ) {
201
+
202
+ const n = keys.length;
203
+ times.unshift( 2 * start - times[ 1 ] );
204
+ times.push( 2 * end - times[ n - 1 ] );
205
+ keys = [ keys[ 1 ], ...keys, keys[ n - 2 ] ];
206
+
207
+ }
208
+
209
+ const vectors = pick => keys.flatMap( key => pick( key ).toArray() );
210
+ return {
211
+ start,
212
+ end,
213
+ position: smooth( VectorKeyframeTrack, '.position', times, vectors( key => key.position ) ),
214
+ target: smooth( VectorKeyframeTrack, '.target', times, vectors( key => key.target ) ),
215
+ fov: smooth( NumberKeyframeTrack, '.fov', times, keys.map( key => key.fov ) ),
216
+ logHeight: smooth( NumberKeyframeTrack, '.orthoHeight', times, keys.map( key => Math.log( key.orthoHeight ) ) ),
217
+ };
218
+
219
+ }
220
+
221
+ _changed() {
222
+
223
+ this.keys.sort( ( a, b ) => a.time - b.time );
224
+ this._interpolants = null;
225
+ this._onChange?.();
226
+
227
+ }
228
+
229
+ }
230
+
231
+ function copyPose( { position, target, fov, orthoHeight } ) {
232
+
233
+ return { position: position.clone(), target: target.clone(), fov, orthoHeight };
234
+
235
+ }
@@ -0,0 +1,146 @@
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
+ /** Every track's keys as plain data, for a saved session. */
109
+ serialize() {
110
+
111
+ return { camera: this.camera.serialize() };
112
+
113
+ }
114
+
115
+ restore( state ) {
116
+
117
+ this.stop();
118
+ this.camera.restore( state?.camera );
119
+
120
+ }
121
+
122
+ /** Drops every key: they belonged to the scene being replaced. */
123
+ clear() {
124
+
125
+ this.stop();
126
+ this.camera.clear();
127
+
128
+ }
129
+
130
+ dispose() {
131
+
132
+ this.stop();
133
+ this._timer.dispose();
134
+ this.camera.dispose();
135
+ this._cameraManager = this._onReset = null;
136
+
137
+ }
138
+
139
+ /** @param {string} [track] - whose keys changed; none for playback */
140
+ _changed( track ) {
141
+
142
+ this.dispatchEvent( { type: EngineEvents.TIMELINE_CHANGED, track } );
143
+
144
+ }
145
+
146
+ }
package/src/version.js ADDED
@@ -0,0 +1,3 @@
1
+ import { version } from '../package.json';
2
+
3
+ export const VERSION = version;