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.
Files changed (81) hide show
  1. package/package.json +2 -2
  2. package/template/package-lock.json +313 -59
  3. package/template/package.json +1 -0
  4. package/template/src/assets/audio/testing_shadow_glow_song.mp3 +0 -0
  5. package/template/src/assets/for_tests/svg/gravity_text.svg +38 -0
  6. package/template/src/assets/for_tests/svg/grip_figure.svg +28 -0
  7. package/template/src/entry.ts +4 -5
  8. package/template/src/example_scenes/alternativesScene.ts +1 -1
  9. package/template/src/example_scenes/dependencyScene.ts +2 -4
  10. package/template/src/example_scenes/fourierSeriesScene.ts +10 -11
  11. package/template/src/example_scenes/keyboardScene.ts +14 -16
  12. package/template/src/example_scenes/latex_text_transitions_scene.ts +146 -0
  13. package/template/src/example_scenes/tests/animations/camera_movements/test_2d_camera_centers_labels.ts +53 -0
  14. package/template/src/example_scenes/tests/animations/camera_movements/test_2d_camera_hits_markers.ts +40 -0
  15. package/template/src/example_scenes/tests/animations/camera_movements/test_camera_rotate_quaternion.ts +17 -0
  16. package/template/src/example_scenes/tests/animations/camera_movements/test_camera_waypoints_sequential.ts +29 -0
  17. package/template/src/example_scenes/tests/animations/camera_movements/test_fly_camera_waypoints_verifiable.ts +87 -0
  18. package/template/src/example_scenes/tests/animations/camera_movements/test_zoom_perspective_sequential.ts +17 -0
  19. package/template/src/example_scenes/tests/animations/latex/test_latex_blue_particle_transition.ts +82 -0
  20. package/template/src/example_scenes/tests/animations/latex/test_latex_highlight_animation.ts +64 -0
  21. package/template/src/example_scenes/tests/animations/latex/test_latex_mark_animation.ts +42 -0
  22. package/template/src/example_scenes/tests/animations/latex/test_latex_particle_transition.ts +48 -0
  23. package/template/src/example_scenes/tests/animations/latex/test_latex_particle_transition_complex.ts +65 -0
  24. package/template/src/example_scenes/tests/animations/latex/test_latex_particle_transition_super_complex.ts +86 -0
  25. package/template/src/example_scenes/tests/animations/latex/test_with_environment_latex_particle_transition.ts +80 -0
  26. package/template/src/example_scenes/tests/animations/latex/test_write_latex_animation.ts +28 -0
  27. package/template/src/example_scenes/tests/animations/latex/test_write_latex_animation_2.ts +34 -0
  28. package/template/src/example_scenes/tests/animations/latex/test_write_latex_animation_3.ts +34 -0
  29. package/template/src/example_scenes/tests/animations/test_updater.ts +24 -0
  30. package/template/src/example_scenes/tests/audio/test_long_audio.ts +11 -0
  31. package/template/src/example_scenes/tests/audio/test_many_short_sounds.ts +50 -0
  32. package/template/src/example_scenes/tests/deferred_anims/testing_deferredAnims.ts +71 -0
  33. package/template/src/example_scenes/tests/deferred_anims/testing_deferredAnims2.ts +65 -0
  34. package/template/src/example_scenes/tests/environment/test_hdri_performance.ts +14 -0
  35. package/template/src/example_scenes/tests/svg/test_basic_latex_query.ts +59 -0
  36. package/template/src/example_scenes/tests/svg/test_basic_svg.ts +11 -0
  37. package/template/src/example_scenes/tests/svg/test_colored_latex_to_svg.ts +42 -0
  38. package/template/src/example_scenes/tests/svg/test_complex_latex_to_svg.ts +22 -0
  39. package/template/src/example_scenes/tests/svg/test_latex_to_svg.ts +17 -0
  40. package/template/src/example_scenes/tests/svg/test_material_on_latex.ts +43 -0
  41. package/template/src/example_scenes/tests/svg/test_query_latex_variables.ts +66 -0
  42. package/template/src/example_scenes/tests/svg/test_regular_text_latex.ts +21 -0
  43. package/template/src/example_scenes/tests/svg/test_super_complex_latex_to_svg.ts +98 -0
  44. package/template/src/example_scenes/tests/svg/test_transition_svgs.ts +33 -0
  45. package/template/src/example_scenes/tests/svg/test_update_svg_object.ts +19 -0
  46. package/template/src/example_scenes/tests/svg/test_yellow_grip_symbol_svg.ts +11 -0
  47. package/template/src/example_scenes/tutorials/easy1.ts +4 -4
  48. package/template/src/example_scenes/tutorials/easy3.ts +1 -1
  49. package/template/src/example_scenes/tutorials/medium1.ts +3 -5
  50. package/template/src/example_scenes/vectorField.ts +2 -4
  51. package/template/src/example_scenes/visulizingFunctions.ts +5 -7
  52. package/template/src/main/index.ts +59 -3
  53. package/template/src/main/rendering.ts +38 -21
  54. package/template/src/preload/index.ts +15 -1
  55. package/template/src/renderer/index.html +1 -1
  56. package/template/src/renderer/src/App.svelte +215 -32
  57. package/template/src/renderer/src/application_assets/360.svg +39 -0
  58. package/template/src/renderer/src/application_assets/move.svg +37 -0
  59. package/template/src/renderer/src/lib/animation/animations.ts +141 -88
  60. package/template/src/renderer/src/lib/animation/captureCanvas.ts +3 -17
  61. package/template/src/renderer/src/lib/animation/interpolations.ts +2 -1
  62. package/template/src/renderer/src/lib/animation/latexMarkAndHighlight.ts +349 -0
  63. package/template/src/renderer/src/lib/animation/latexTransitionsAndWrite.ts +558 -0
  64. package/template/src/renderer/src/lib/audio/manager.ts +185 -0
  65. package/template/src/renderer/src/lib/general/helpers.ts +16 -47
  66. package/template/src/renderer/src/lib/rendering/hdri.ts +273 -0
  67. package/template/src/renderer/src/lib/rendering/lighting3d.ts +0 -105
  68. package/template/src/renderer/src/lib/rendering/setup.ts +7 -1
  69. package/template/src/renderer/src/lib/rendering/svg/latexSVGQueries.ts +44 -0
  70. package/template/src/renderer/src/lib/rendering/svg/latexToSVG.ts +132 -0
  71. package/template/src/renderer/src/lib/rendering/svg/svgObjectHelpers.ts +59 -0
  72. package/template/src/renderer/src/lib/rendering/svg/svgRendering.ts +120 -0
  73. package/template/src/renderer/src/lib/scene/sceneClass.ts +180 -62
  74. package/template/src/renderer/src/lib/animation/helpers.ts +0 -7
  75. package/template/src/renderer/src/lib/audio/loader.ts +0 -104
  76. package/template/src/renderer/src/lib/rendering/materials.ts +0 -6
  77. package/template/src/renderer/src/lib/rendering/protocols.ts +0 -21
  78. package/template/src/renderer/src/lib/rendering/svg/drawing.ts +0 -213
  79. package/template/src/renderer/src/lib/rendering/svg/parsing.ts +0 -717
  80. package/template/src/renderer/src/lib/rendering/svg/rastered.ts +0 -42
  81. 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, logCameraState } from '../general/helpers'
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 { animationFPSThrottle, renderSkip } from '../../../../entry'
14
+ import { animationFPSDivider, renderSkip } from '../../../../entry'
15
15
  import { addDestroyFunction } from '../general/onDestory'
16
- import { ticksToMillis } from '../animation/helpers'
17
- import { AudioInScene, loadAllAudio, playAudio, registerAudio } from '../audio/loader'
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.traceFromStart = hotReloadSetting === HotReloadSetting.TraceFromStart
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.buildFunction = async () => {
114
- await buildFunctionGiven(this)
115
- this.end()
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
- this.scene = scene
129
- this.camera = camera
130
- this.renderer = renderer
131
- this.controls = controls
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
- addAnim(...animations: UserAnimation[]) {
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
- insertAnimAt(tick: number, ...animations: UserAnimation[]) {
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 + 1
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.addAnim(createAnim(easeConstant(0, duration), () => {}))
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
- // Get the direction vector (works for both camera types)
265
- const direction = new THREE.Vector3(0, 0, -1)
336
+ const dir = new THREE.Vector3();
337
+ this.camera.getWorldDirection(dir); // works for both camera types
266
338
 
267
- // Use the appropriate transformation based on camera type
268
- if (this.camera.type === 'OrthographicCamera') {
269
- direction.transformDirection(this.camera.matrixWorld)
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
- // Calculate the new target (same for both camera types)
275
- const targetDistance = this.controls.target.distanceTo(this.controls.object.position)
276
- const newTarget = this.camera.position.clone().add(direction.multiplyScalar(targetDistance))
277
- this.controls.target.copy(newTarget)
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
- const internalAspect = this.pixelsWidth / this.pixelsHeight
337
- // Resize handler
338
- const handleResize = () => {
339
- const newWidth = container.clientWidth
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
- this.camera.updateProjectionMatrix()
350
- this.renderer.setSize(newWidth, newHeight)
351
- this.renderer.render(this.scene, this.camera)
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
- window.addEventListener('resize', handleResize)
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
- this.startControls()
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
- this.isPlaying = false
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
- logCameraState(this.camera)
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
- if (numberCalledAnimate % animationFPSThrottle === 0) {
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,6 +0,0 @@
1
- import * as THREE from "three"
2
- import { COLORS } from "./helpers"
3
-
4
- export const getRegularMetal = (color: number = COLORS.white) => {
5
- return new THREE.MeshStandardMaterial({ color: color, roughness: 0.2, metalness: 1 })
6
- }
@@ -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
- }