create-definedmotion 0.1.4 → 0.3.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/package.json +2 -2
- package/template/package-lock.json +313 -59
- package/template/package.json +1 -0
- package/template/src/assets/audio/testing_shadow_glow_song.mp3 +0 -0
- package/template/src/assets/for_tests/svg/gravity_text.svg +38 -0
- package/template/src/assets/for_tests/svg/grip_figure.svg +28 -0
- package/template/src/entry.ts +4 -5
- package/template/src/example_scenes/alternativesScene.ts +1 -1
- package/template/src/example_scenes/dependencyScene.ts +2 -4
- package/template/src/example_scenes/fourierSeriesScene.ts +10 -11
- package/template/src/example_scenes/keyboardScene.ts +14 -16
- package/template/src/example_scenes/latex_text_transitions_scene.ts +146 -0
- package/template/src/example_scenes/tests/animations/camera_movements/test_2d_camera_centers_labels.ts +53 -0
- package/template/src/example_scenes/tests/animations/camera_movements/test_2d_camera_hits_markers.ts +40 -0
- package/template/src/example_scenes/tests/animations/camera_movements/test_camera_rotate_quaternion.ts +17 -0
- package/template/src/example_scenes/tests/animations/camera_movements/test_camera_waypoints_sequential.ts +29 -0
- package/template/src/example_scenes/tests/animations/camera_movements/test_fly_camera_waypoints_verifiable.ts +87 -0
- package/template/src/example_scenes/tests/animations/camera_movements/test_zoom_perspective_sequential.ts +17 -0
- package/template/src/example_scenes/tests/animations/latex/test_latex_blue_particle_transition.ts +82 -0
- package/template/src/example_scenes/tests/animations/latex/test_latex_highlight_animation.ts +64 -0
- package/template/src/example_scenes/tests/animations/latex/test_latex_mark_animation.ts +42 -0
- package/template/src/example_scenes/tests/animations/latex/test_latex_particle_transition.ts +48 -0
- package/template/src/example_scenes/tests/animations/latex/test_latex_particle_transition_complex.ts +65 -0
- package/template/src/example_scenes/tests/animations/latex/test_latex_particle_transition_super_complex.ts +86 -0
- package/template/src/example_scenes/tests/animations/latex/test_with_environment_latex_particle_transition.ts +80 -0
- package/template/src/example_scenes/tests/animations/latex/test_write_latex_animation.ts +28 -0
- package/template/src/example_scenes/tests/animations/latex/test_write_latex_animation_2.ts +34 -0
- package/template/src/example_scenes/tests/animations/latex/test_write_latex_animation_3.ts +34 -0
- package/template/src/example_scenes/tests/animations/test_updater.ts +24 -0
- package/template/src/example_scenes/tests/audio/test_long_audio.ts +11 -0
- package/template/src/example_scenes/tests/audio/test_many_short_sounds.ts +50 -0
- package/template/src/example_scenes/tests/deferred_anims/testing_deferredAnims.ts +71 -0
- package/template/src/example_scenes/tests/deferred_anims/testing_deferredAnims2.ts +65 -0
- package/template/src/example_scenes/tests/environment/test_hdri_performance.ts +14 -0
- package/template/src/example_scenes/tests/svg/test_basic_latex_query.ts +59 -0
- package/template/src/example_scenes/tests/svg/test_basic_svg.ts +11 -0
- package/template/src/example_scenes/tests/svg/test_colored_latex_to_svg.ts +42 -0
- package/template/src/example_scenes/tests/svg/test_complex_latex_to_svg.ts +22 -0
- package/template/src/example_scenes/tests/svg/test_latex_to_svg.ts +17 -0
- package/template/src/example_scenes/tests/svg/test_material_on_latex.ts +43 -0
- package/template/src/example_scenes/tests/svg/test_query_latex_variables.ts +66 -0
- package/template/src/example_scenes/tests/svg/test_regular_text_latex.ts +21 -0
- package/template/src/example_scenes/tests/svg/test_super_complex_latex_to_svg.ts +98 -0
- package/template/src/example_scenes/tests/svg/test_transition_svgs.ts +33 -0
- package/template/src/example_scenes/tests/svg/test_update_svg_object.ts +19 -0
- package/template/src/example_scenes/tests/svg/test_yellow_grip_symbol_svg.ts +11 -0
- package/template/src/example_scenes/tutorials/easy1.ts +4 -4
- package/template/src/example_scenes/tutorials/easy3.ts +1 -1
- package/template/src/example_scenes/tutorials/medium1.ts +3 -5
- package/template/src/example_scenes/vectorField.ts +2 -4
- package/template/src/example_scenes/visulizingFunctions.ts +5 -7
- package/template/src/main/index.ts +59 -3
- package/template/src/main/rendering.ts +38 -21
- package/template/src/preload/index.ts +15 -1
- package/template/src/renderer/index.html +1 -1
- package/template/src/renderer/src/App.svelte +215 -32
- package/template/src/renderer/src/application_assets/360.svg +39 -0
- package/template/src/renderer/src/application_assets/move.svg +37 -0
- package/template/src/renderer/src/lib/animation/animations.ts +141 -88
- package/template/src/renderer/src/lib/animation/captureCanvas.ts +3 -17
- package/template/src/renderer/src/lib/animation/interpolations.ts +2 -1
- package/template/src/renderer/src/lib/animation/latexMarkAndHighlight.ts +349 -0
- package/template/src/renderer/src/lib/animation/latexTransitionsAndWrite.ts +558 -0
- package/template/src/renderer/src/lib/audio/manager.ts +185 -0
- package/template/src/renderer/src/lib/general/helpers.ts +16 -47
- package/template/src/renderer/src/lib/rendering/hdri.ts +273 -0
- package/template/src/renderer/src/lib/rendering/lighting3d.ts +0 -105
- package/template/src/renderer/src/lib/rendering/setup.ts +7 -1
- package/template/src/renderer/src/lib/rendering/svg/latexSVGQueries.ts +44 -0
- package/template/src/renderer/src/lib/rendering/svg/latexToSVG.ts +132 -0
- package/template/src/renderer/src/lib/rendering/svg/svgObjectHelpers.ts +59 -0
- package/template/src/renderer/src/lib/rendering/svg/svgRendering.ts +120 -0
- package/template/src/renderer/src/lib/scene/sceneClass.ts +180 -62
- package/template/src/renderer/src/lib/animation/helpers.ts +0 -7
- package/template/src/renderer/src/lib/audio/loader.ts +0 -104
- package/template/src/renderer/src/lib/rendering/materials.ts +0 -6
- package/template/src/renderer/src/lib/rendering/protocols.ts +0 -21
- package/template/src/renderer/src/lib/rendering/svg/drawing.ts +0 -213
- package/template/src/renderer/src/lib/rendering/svg/parsing.ts +0 -717
- package/template/src/renderer/src/lib/rendering/svg/rastered.ts +0 -42
- package/template/src/renderer/src/lib/rendering/svgObjects.ts +0 -1137
|
@@ -5,16 +5,37 @@ import {
|
|
|
5
5
|
type InternalAnimation,
|
|
6
6
|
type UserAnimation
|
|
7
7
|
} from '../animation/protocols'
|
|
8
|
-
import { generateID
|
|
8
|
+
import { generateID } from '../general/helpers'
|
|
9
9
|
import { sleep } from '../rendering/helpers'
|
|
10
10
|
import { createScene } from '../rendering/setup'
|
|
11
11
|
import * as THREE from 'three'
|
|
12
12
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
|
13
13
|
import { easeConstant } from '../animation/interpolations'
|
|
14
|
-
import {
|
|
14
|
+
import { animationFPSDivider, renderSkip } from '../../../../entry'
|
|
15
15
|
import { addDestroyFunction } from '../general/onDestory'
|
|
16
|
-
import {
|
|
17
|
-
|
|
16
|
+
import {
|
|
17
|
+
AudioInScene,
|
|
18
|
+
loadAllAudio,
|
|
19
|
+
playAudio,
|
|
20
|
+
registerAudio,
|
|
21
|
+
seekToTick as audioSeekToTick,
|
|
22
|
+
pauseAll as audioPauseAll,
|
|
23
|
+
resumeAll as audioResumeAll,
|
|
24
|
+
stopAll as audioStopAll
|
|
25
|
+
} from '../audio/manager'
|
|
26
|
+
|
|
27
|
+
export const screenFPS = await (window.api as any).getDisplayHz(); //Your screen fps
|
|
28
|
+
|
|
29
|
+
const timelineFPS = screenFPS / animationFPSDivider;
|
|
30
|
+
|
|
31
|
+
// Convert ticks (frames) to milliseconds
|
|
32
|
+
export const ticksToMillis = (ticks: number) => (ticks / timelineFPS) * 1000
|
|
33
|
+
|
|
34
|
+
// Convert milliseconds to the closest whole number of ticks
|
|
35
|
+
export const millisToTicks = (ms: number) => Math.ceil((ms / 1000) * timelineFPS)
|
|
36
|
+
|
|
37
|
+
export const renderOutputFps = () => timelineFPS / renderSkip
|
|
38
|
+
|
|
18
39
|
|
|
19
40
|
export enum SpaceSetting {
|
|
20
41
|
ThreeDim,
|
|
@@ -23,7 +44,19 @@ export enum SpaceSetting {
|
|
|
23
44
|
|
|
24
45
|
export enum HotReloadSetting {
|
|
25
46
|
TraceFromStart,
|
|
26
|
-
BeginFromCurrent
|
|
47
|
+
BeginFromCurrent,
|
|
48
|
+
BeginFreshOnSave
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const hotreloadNameLookup = (mode: HotReloadSetting) => {
|
|
52
|
+
switch (mode) {
|
|
53
|
+
case HotReloadSetting.TraceFromStart:
|
|
54
|
+
return "Trace from start";
|
|
55
|
+
case HotReloadSetting.BeginFromCurrent:
|
|
56
|
+
return "Begin from current frame without trace";
|
|
57
|
+
case HotReloadSetting.BeginFreshOnSave:
|
|
58
|
+
return "Go to the beginning";
|
|
59
|
+
}
|
|
27
60
|
}
|
|
28
61
|
|
|
29
62
|
type SceneInstruction = (tick: number) => any
|
|
@@ -54,6 +87,8 @@ export class AnimatedScene {
|
|
|
54
87
|
|
|
55
88
|
playEffectFunction: () => any = () => {}
|
|
56
89
|
|
|
90
|
+
renderingEventFunction: (start: boolean) => any = () => {}
|
|
91
|
+
|
|
57
92
|
isPlaying = false
|
|
58
93
|
|
|
59
94
|
private initialSceneChildren: THREE.Object3D[] = []
|
|
@@ -61,6 +96,7 @@ export class AnimatedScene {
|
|
|
61
96
|
position: THREE.Vector3
|
|
62
97
|
rotation: THREE.Euler
|
|
63
98
|
zoom?: number
|
|
99
|
+
fov?: number
|
|
64
100
|
left?: number
|
|
65
101
|
right?: number
|
|
66
102
|
top?: number
|
|
@@ -77,6 +113,7 @@ export class AnimatedScene {
|
|
|
77
113
|
|
|
78
114
|
private buildFunction: (scene: this) => any
|
|
79
115
|
|
|
116
|
+
public hotReloadSetting: HotReloadSetting
|
|
80
117
|
private traceFromStart: boolean
|
|
81
118
|
|
|
82
119
|
private controlsAnimationFrameId: number | null = null
|
|
@@ -87,6 +124,11 @@ export class AnimatedScene {
|
|
|
87
124
|
private doNotPlayAudio = false
|
|
88
125
|
private renderingAudioGather: AudioInScene[] = []
|
|
89
126
|
|
|
127
|
+
private playbackTargetDistance: number | null = null
|
|
128
|
+
|
|
129
|
+
private resizeObserver?: ResizeObserver
|
|
130
|
+
|
|
131
|
+
|
|
90
132
|
constructor(
|
|
91
133
|
pixelsWidth: number,
|
|
92
134
|
pixelsHeight: number,
|
|
@@ -97,7 +139,8 @@ export class AnimatedScene {
|
|
|
97
139
|
this.container = globalContainerRef
|
|
98
140
|
this.pixelsHeight = pixelsHeight
|
|
99
141
|
this.pixelsWidth = pixelsWidth
|
|
100
|
-
this.
|
|
142
|
+
this.hotReloadSetting = hotReloadSetting
|
|
143
|
+
this.traceFromStart = hotReloadSetting !== HotReloadSetting.BeginFromCurrent
|
|
101
144
|
|
|
102
145
|
const threeDim = spaceSetting === SpaceSetting.ThreeDim
|
|
103
146
|
|
|
@@ -110,12 +153,11 @@ export class AnimatedScene {
|
|
|
110
153
|
this.farLimitRender
|
|
111
154
|
)
|
|
112
155
|
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
156
|
+
this.scene = scene
|
|
157
|
+
this.camera = camera
|
|
158
|
+
this.renderer = renderer
|
|
159
|
+
this.controls = controls
|
|
117
160
|
|
|
118
|
-
this.attachScreenSizeListener(globalContainerRef, threeDim)
|
|
119
161
|
// Store initial state
|
|
120
162
|
this.initialSceneChildren = [...scene.children]
|
|
121
163
|
this.initialCameraState = this.captureCameraState(camera)
|
|
@@ -125,10 +167,13 @@ export class AnimatedScene {
|
|
|
125
167
|
shadowMapEnabled: renderer.shadowMap.enabled
|
|
126
168
|
}
|
|
127
169
|
|
|
128
|
-
|
|
129
|
-
this.
|
|
130
|
-
|
|
131
|
-
|
|
170
|
+
|
|
171
|
+
this.buildFunction = async () => {
|
|
172
|
+
await buildFunctionGiven(this)
|
|
173
|
+
this.end()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this.attachScreenSizeListener(globalContainerRef, threeDim)
|
|
132
177
|
|
|
133
178
|
this.startControls()
|
|
134
179
|
|
|
@@ -148,7 +193,16 @@ export class AnimatedScene {
|
|
|
148
193
|
this.appendInstruction(instruction, this.sceneCalculationTick)
|
|
149
194
|
}
|
|
150
195
|
|
|
151
|
-
|
|
196
|
+
doAt(tick: number, instruction: SceneInstruction) {
|
|
197
|
+
if (tick < 0) throw new Error('doAt: tick must be ≥ 0')
|
|
198
|
+
this.appendInstruction(instruction, tick)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getCurrentTimeMs() {
|
|
202
|
+
return ticksToMillis(this.sceneRenderTick)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
addAnims(...animations: UserAnimation[]) {
|
|
152
206
|
const longest = Math.max(...animations.map((a) => a.interpolation.length))
|
|
153
207
|
for (const animation of animations) {
|
|
154
208
|
this.appendAnimation(animation)
|
|
@@ -156,7 +210,7 @@ export class AnimatedScene {
|
|
|
156
210
|
this.sceneCalculationTick += longest
|
|
157
211
|
}
|
|
158
212
|
|
|
159
|
-
|
|
213
|
+
insertAnimsAt(tick: number, ...animations: UserAnimation[]) {
|
|
160
214
|
for (const animation of animations) {
|
|
161
215
|
const internalAnimation: InternalAnimation = {
|
|
162
216
|
startTick: tick,
|
|
@@ -169,6 +223,21 @@ export class AnimatedScene {
|
|
|
169
223
|
}
|
|
170
224
|
}
|
|
171
225
|
|
|
226
|
+
addDeferredAnims(...futureAnimations: (() => UserAnimation)[]) {
|
|
227
|
+
// Execute once during planning just to get durations
|
|
228
|
+
const tempAnims = futureAnimations.map(fn => fn())
|
|
229
|
+
const longest = Math.max(...tempAnims.map((a) => a.interpolation.length))
|
|
230
|
+
|
|
231
|
+
this.do((tick) => {
|
|
232
|
+
const calculatedAnimations: UserAnimation[] = []
|
|
233
|
+
for (const futureAnimation of futureAnimations) {
|
|
234
|
+
calculatedAnimations.push(futureAnimation()) // Execute again at runtime
|
|
235
|
+
}
|
|
236
|
+
this.insertAnimsAt(tick, ...calculatedAnimations)
|
|
237
|
+
})
|
|
238
|
+
this.sceneCalculationTick += longest
|
|
239
|
+
}
|
|
240
|
+
|
|
172
241
|
addSequentialBackgroundAnims(...sequentialAnimations: UserAnimation[]) {
|
|
173
242
|
let padding = 0
|
|
174
243
|
for (const animation of sequentialAnimations) {
|
|
@@ -182,7 +251,7 @@ export class AnimatedScene {
|
|
|
182
251
|
}
|
|
183
252
|
|
|
184
253
|
end() {
|
|
185
|
-
this.totalSceneTicks = this.sceneCalculationTick
|
|
254
|
+
this.totalSceneTicks = this.sceneCalculationTick
|
|
186
255
|
}
|
|
187
256
|
|
|
188
257
|
registerAudio(audioPath: string) {
|
|
@@ -221,7 +290,7 @@ export class AnimatedScene {
|
|
|
221
290
|
}
|
|
222
291
|
|
|
223
292
|
addWait(duration: number) {
|
|
224
|
-
this.
|
|
293
|
+
this.addAnims(createAnim(easeConstant(0, duration), () => {}))
|
|
225
294
|
}
|
|
226
295
|
|
|
227
296
|
async jumpToFrameAtIndex(index: number, notSize: boolean = false) {
|
|
@@ -251,9 +320,12 @@ export class AnimatedScene {
|
|
|
251
320
|
this.sceneRenderTick = index
|
|
252
321
|
await this.playEffectFunction()
|
|
253
322
|
|
|
254
|
-
// console.log('INSTRUCTIONS', this.sceneInstructions)
|
|
255
|
-
|
|
256
323
|
this.doNotPlayAudio = false
|
|
324
|
+
|
|
325
|
+
// Only (re)start audio when actively playing or rendering
|
|
326
|
+
if (this.isPlaying && !this.doNotPlayAudio && !this.isRendering) {
|
|
327
|
+
audioSeekToTick(this.sceneRenderTick, this.planedSounds, timelineFPS)
|
|
328
|
+
}
|
|
257
329
|
}
|
|
258
330
|
|
|
259
331
|
getAspectRatio() {
|
|
@@ -261,24 +333,17 @@ export class AnimatedScene {
|
|
|
261
333
|
}
|
|
262
334
|
|
|
263
335
|
private syncControlsWithCamera() {
|
|
264
|
-
|
|
265
|
-
|
|
336
|
+
const dir = new THREE.Vector3();
|
|
337
|
+
this.camera.getWorldDirection(dir); // works for both camera types
|
|
266
338
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
} else {
|
|
271
|
-
direction.applyQuaternion(this.camera.quaternion)
|
|
272
|
-
}
|
|
339
|
+
const distance =
|
|
340
|
+
this.playbackTargetDistance ??
|
|
341
|
+
this.controls.target.distanceTo(this.camera.position);
|
|
273
342
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// Reset the internal state
|
|
280
|
-
this.controls.update()
|
|
281
|
-
}
|
|
343
|
+
const newTarget = this.camera.position.clone().add(dir.multiplyScalar(distance));
|
|
344
|
+
this.controls.target.copy(newTarget);
|
|
345
|
+
this.controls.update();
|
|
346
|
+
}
|
|
282
347
|
|
|
283
348
|
private startControls() {
|
|
284
349
|
this.controls.enabled = true
|
|
@@ -317,10 +382,6 @@ export class AnimatedScene {
|
|
|
317
382
|
|
|
318
383
|
this.renderCurrentFrame()
|
|
319
384
|
animateCounter++
|
|
320
|
-
|
|
321
|
-
if (animateCounter % 10 === 0) {
|
|
322
|
-
logCameraState(this.camera)
|
|
323
|
-
}
|
|
324
385
|
}
|
|
325
386
|
animate()
|
|
326
387
|
}
|
|
@@ -333,36 +394,68 @@ export class AnimatedScene {
|
|
|
333
394
|
}
|
|
334
395
|
|
|
335
396
|
private attachScreenSizeListener(container: HTMLElement, threeDim: boolean) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const newHeight = container.clientHeight
|
|
341
|
-
|
|
342
|
-
if (threeDim && this.camera instanceof THREE.PerspectiveCamera) {
|
|
343
|
-
this.camera.aspect = internalAspect
|
|
344
|
-
} else if (this.camera instanceof THREE.OrthographicCamera) {
|
|
345
|
-
this.camera.left = -this.zoom * internalAspect
|
|
346
|
-
this.camera.right = this.zoom * internalAspect
|
|
347
|
-
}
|
|
397
|
+
const targetAspect = this.pixelsWidth / this.pixelsHeight
|
|
398
|
+
|
|
399
|
+
const handleResize = (width: number) => {
|
|
400
|
+
if (!width) return
|
|
348
401
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
402
|
+
// Respect the animation's logical aspect ratio
|
|
403
|
+
const height = width / targetAspect
|
|
404
|
+
|
|
405
|
+
// Set container size manually
|
|
406
|
+
container.style.height = `${height}px`
|
|
407
|
+
|
|
408
|
+
// Update camera based on that aspect
|
|
409
|
+
const aspect = width / height
|
|
410
|
+
|
|
411
|
+
if (threeDim && this.camera instanceof THREE.PerspectiveCamera) {
|
|
412
|
+
this.camera.aspect = aspect
|
|
413
|
+
} else if (this.camera instanceof THREE.OrthographicCamera) {
|
|
414
|
+
this.camera.left = -this.zoom * aspect
|
|
415
|
+
this.camera.right = this.zoom * aspect
|
|
416
|
+
this.camera.top = this.zoom
|
|
417
|
+
this.camera.bottom = -this.zoom
|
|
352
418
|
}
|
|
353
|
-
|
|
419
|
+
|
|
420
|
+
this.camera.updateProjectionMatrix()
|
|
421
|
+
this.renderer.setSize(width, height)
|
|
422
|
+
this.renderer.render(this.scene, this.camera)
|
|
354
423
|
}
|
|
355
424
|
|
|
425
|
+
// Initial sizing
|
|
426
|
+
handleResize(container.clientWidth)
|
|
427
|
+
|
|
428
|
+
// React to container size changes (e.g. inspector open/close)
|
|
429
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
430
|
+
for (const entry of entries) {
|
|
431
|
+
const { width } = entry.contentRect
|
|
432
|
+
handleResize(width)
|
|
433
|
+
}
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
this.resizeObserver.observe(container)
|
|
437
|
+
|
|
438
|
+
// Clean up on destroy / hot reload
|
|
439
|
+
addDestroyFunction(() => {
|
|
440
|
+
this.resizeObserver?.disconnect()
|
|
441
|
+
})
|
|
442
|
+
}
|
|
443
|
+
|
|
356
444
|
pause() {
|
|
357
445
|
this.isPlaying = false
|
|
358
446
|
if (this.animationFrameId) cancelAnimationFrame(this.animationFrameId)
|
|
359
447
|
|
|
360
|
-
this.syncControlsWithCamera()
|
|
361
448
|
|
|
362
|
-
|
|
449
|
+
audioPauseAll()
|
|
450
|
+
// use the captured distance one last time
|
|
451
|
+
this.syncControlsWithCamera();
|
|
452
|
+
this.playbackTargetDistance = null;
|
|
453
|
+
|
|
454
|
+
this.startControls();
|
|
363
455
|
}
|
|
364
456
|
|
|
365
457
|
async render() {
|
|
458
|
+
this.renderingEventFunction(true)
|
|
366
459
|
this.isRendering = true
|
|
367
460
|
this.isPlaying = true
|
|
368
461
|
this.stopControls()
|
|
@@ -420,11 +513,13 @@ export class AnimatedScene {
|
|
|
420
513
|
div.style.zIndex = originalZIndex
|
|
421
514
|
|
|
422
515
|
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight)
|
|
516
|
+
this.isPlaying = false
|
|
423
517
|
await this.jumpToFrameAtIndex(0)
|
|
424
518
|
this.renderCurrentFrame()
|
|
425
519
|
|
|
426
|
-
|
|
520
|
+
|
|
427
521
|
this.startControls()
|
|
522
|
+
this.renderingEventFunction(false)
|
|
428
523
|
}
|
|
429
524
|
|
|
430
525
|
play() {
|
|
@@ -435,17 +530,35 @@ export class AnimatedScene {
|
|
|
435
530
|
this.isPlaying = true
|
|
436
531
|
this.stopControls()
|
|
437
532
|
await this.jumpToFrameAtIndex(fromFrame)
|
|
438
|
-
|
|
533
|
+
|
|
534
|
+
// If we were previously paused and had partial offsets captured, this also ensures clean resume:
|
|
535
|
+
audioResumeAll()
|
|
536
|
+
|
|
537
|
+
// Capture a distance that OrbitControls will keep during play
|
|
538
|
+
this.playbackTargetDistance =
|
|
539
|
+
this.controls.target.distanceTo(this.camera.position)
|
|
439
540
|
|
|
440
541
|
let currentFrame = fromFrame
|
|
441
542
|
let numberCalledAnimate = 0
|
|
442
543
|
const animate = async (trace: boolean) => {
|
|
443
544
|
if (!this.isPlaying) return
|
|
444
545
|
if (currentFrame <= toFrame) {
|
|
445
|
-
|
|
546
|
+
// Still modulus since the requestAnimationFrame runs at the screenFPS rate, not timelineFPS rate
|
|
547
|
+
if (numberCalledAnimate % animationFPSDivider === 0) {
|
|
446
548
|
this.sceneRenderTick = currentFrame
|
|
447
549
|
//To not apply trace twice if we just jumped to startframe (and thus tranced it)
|
|
448
550
|
await this.traceCurrentFrame(this.sceneRenderTick, true, !trace)
|
|
551
|
+
|
|
552
|
+
// --- Keep controls.target aligned with the animated camera ---
|
|
553
|
+
if (this.playbackTargetDistance != null) {
|
|
554
|
+
const camDir = new THREE.Vector3()
|
|
555
|
+
this.camera.getWorldDirection(camDir) // forward (-Z in view space)
|
|
556
|
+
const target = this.camera.position.clone()
|
|
557
|
+
.add(camDir.multiplyScalar(this.playbackTargetDistance))
|
|
558
|
+
this.controls.target.copy(target)
|
|
559
|
+
this.controls.update() // ok to call while disabled; just updates internals
|
|
560
|
+
}
|
|
561
|
+
|
|
449
562
|
this.renderCurrentFrame()
|
|
450
563
|
currentFrame++
|
|
451
564
|
await this.playEffectFunction()
|
|
@@ -537,6 +650,7 @@ export class AnimatedScene {
|
|
|
537
650
|
|
|
538
651
|
// Replace recreateComponents with reset logic
|
|
539
652
|
private resetComponents(notSize: boolean) {
|
|
653
|
+
audioStopAll()
|
|
540
654
|
this.resetSceneVars()
|
|
541
655
|
this.resetScene()
|
|
542
656
|
this.resetCamera()
|
|
@@ -567,6 +681,7 @@ export class AnimatedScene {
|
|
|
567
681
|
state.bottom = camera.bottom
|
|
568
682
|
} else if (camera instanceof THREE.PerspectiveCamera) {
|
|
569
683
|
state.zoom = camera.zoom
|
|
684
|
+
state.fov = (camera as THREE.PerspectiveCamera).fov
|
|
570
685
|
}
|
|
571
686
|
|
|
572
687
|
return state
|
|
@@ -607,6 +722,9 @@ export class AnimatedScene {
|
|
|
607
722
|
|
|
608
723
|
if (cam instanceof THREE.PerspectiveCamera) {
|
|
609
724
|
cam.zoom = this.initialCameraState.zoom!
|
|
725
|
+
if (this.initialCameraState.fov != null) {
|
|
726
|
+
(cam as THREE.PerspectiveCamera).fov = this.initialCameraState.fov
|
|
727
|
+
}
|
|
610
728
|
}
|
|
611
729
|
}
|
|
612
730
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { screenFps } from '../../../../entry'
|
|
2
|
-
|
|
3
|
-
// Convert ticks (frames) to milliseconds
|
|
4
|
-
export const ticksToMillis = (ticks: number) => (ticks / screenFps) * 1000
|
|
5
|
-
|
|
6
|
-
// Convert milliseconds to the closest whole number of ticks
|
|
7
|
-
export const millisToTicks = (ms: number) => Math.ceil((ms / 1000) * screenFps)
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
export interface AudioInScene {
|
|
2
|
-
audioPath: string
|
|
3
|
-
volume: number
|
|
4
|
-
atFrame: number
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// Global caches for audio assets
|
|
8
|
-
// The key is a file path and the value is the loaded AudioBuffer.
|
|
9
|
-
let loadedAudio = new Map<string, AudioBuffer>()
|
|
10
|
-
|
|
11
|
-
let registeredAudios = new Set<string>()
|
|
12
|
-
|
|
13
|
-
// Create or reuse an AudioContext instance.
|
|
14
|
-
const audioContext = new AudioContext()
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Registers an audio file path and returns a corresponding ID.
|
|
18
|
-
* @param audioPath - The path to the audio file.
|
|
19
|
-
* @returns A unique audio ID.
|
|
20
|
-
*/
|
|
21
|
-
export const registerAudio = (audioPath: string) => {
|
|
22
|
-
registeredAudios.add(audioPath)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Loads all registered audio files into the global cache.
|
|
27
|
-
* Every audio file referenced in the audioIDMappings will be fetched
|
|
28
|
-
* and decoded; results are stored in the loadedAudio map.
|
|
29
|
-
*/
|
|
30
|
-
export const loadAllAudio = async (): Promise<void> => {
|
|
31
|
-
const loadPromises: Promise<void>[] = []
|
|
32
|
-
|
|
33
|
-
// Loop over each registered audio file.
|
|
34
|
-
for (const [path, _] of registeredAudios.entries()) {
|
|
35
|
-
// If this audio file isn't already loaded, load it.
|
|
36
|
-
if (!loadedAudio.has(path)) {
|
|
37
|
-
const promise = fetch(path)
|
|
38
|
-
.then((response) => {
|
|
39
|
-
if (!response.ok) {
|
|
40
|
-
throw new Error(`Failed to load audio file at ${path}: ${response.statusText}`)
|
|
41
|
-
}
|
|
42
|
-
return response.arrayBuffer()
|
|
43
|
-
})
|
|
44
|
-
.then((arrayBuffer) => audioContext.decodeAudioData(arrayBuffer))
|
|
45
|
-
.then((decodedBuffer) => {
|
|
46
|
-
loadedAudio.set(path, decodedBuffer)
|
|
47
|
-
console.log(`Loaded audio [path: ${path}] from: ${path}`)
|
|
48
|
-
})
|
|
49
|
-
.catch((error) => {
|
|
50
|
-
console.error(`Error loading audio from ${path}:`, error)
|
|
51
|
-
})
|
|
52
|
-
loadPromises.push(promise)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
await Promise.all(loadPromises)
|
|
57
|
-
console.log('All audio files have been loaded.')
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Plays the audio corresponding to the given audioID with the specified volume.
|
|
62
|
-
* @param audioID - The registered audio ID to play.
|
|
63
|
-
* @param volume - The volume level (e.g., 0.0 to 1.0).
|
|
64
|
-
*/
|
|
65
|
-
export const playAudio = (audioPath: string, volume: number) => {
|
|
66
|
-
const hasAudio = registeredAudios.has(audioPath)
|
|
67
|
-
if (!hasAudio) {
|
|
68
|
-
console.warn(`Audio path ${audioPath} not registered.`)
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const buffer = loadedAudio.get(audioPath)
|
|
73
|
-
if (!buffer) {
|
|
74
|
-
console.warn(`Audio file for ${audioPath} has not been loaded.`)
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Create a buffer source node to play the audio.
|
|
79
|
-
const source = audioContext.createBufferSource()
|
|
80
|
-
source.buffer = buffer
|
|
81
|
-
|
|
82
|
-
// Create a gain node to control the volume.
|
|
83
|
-
const gainNode = audioContext.createGain()
|
|
84
|
-
gainNode.gain.value = volume
|
|
85
|
-
|
|
86
|
-
// Connect the nodes: source -> gain -> audio context destination.
|
|
87
|
-
source.connect(gainNode)
|
|
88
|
-
gainNode.connect(audioContext.destination)
|
|
89
|
-
|
|
90
|
-
// Start playback immediately.
|
|
91
|
-
source.start(0)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Cleans up all audio resources.
|
|
96
|
-
* This function:
|
|
97
|
-
* - Clears the loaded audio cache.
|
|
98
|
-
* - Clears the audio ID mapping.
|
|
99
|
-
* - Resets the last registered ID counter.
|
|
100
|
-
*/
|
|
101
|
-
export const cleanupAudioData = async (): Promise<void> => {
|
|
102
|
-
// Clear the caches.
|
|
103
|
-
loadedAudio.clear()
|
|
104
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import * as THREE from 'three'
|
|
2
|
-
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
|
3
|
-
|
|
4
|
-
export interface SceneComponents {
|
|
5
|
-
camera: THREE.PerspectiveCamera | THREE.OrthographicCamera
|
|
6
|
-
renderer: THREE.WebGLRenderer
|
|
7
|
-
scene: THREE.Scene
|
|
8
|
-
controls: OrbitControls
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const RERENDER = (state: SceneComponents) => {
|
|
12
|
-
state.renderer.render(state.scene, state.camera)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const ADD = (state: SceneComponents, element: THREE.Mesh) => {
|
|
16
|
-
state.scene.add(element)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const REMOVE = (state: SceneComponents, element: THREE.Mesh) => {
|
|
20
|
-
state.scene.remove(element)
|
|
21
|
-
}
|