create-definedmotion 0.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 (98) hide show
  1. package/bin/index.js +3 -0
  2. package/package.json +37 -0
  3. package/src/cli.js +100 -0
  4. package/template/.editorconfig +9 -0
  5. package/template/.prettierignore +6 -0
  6. package/template/.prettierrc.yaml +10 -0
  7. package/template/_gitignore +10 -0
  8. package/template/build/entitlements.mac.plist +12 -0
  9. package/template/build/icon.icns +0 -0
  10. package/template/build/icon.ico +0 -0
  11. package/template/build/icon.png +0 -0
  12. package/template/electron-builder.yml +43 -0
  13. package/template/electron.vite.config.ts +50 -0
  14. package/template/eslint.config.mjs +24 -0
  15. package/template/package-lock.json +10299 -0
  16. package/template/package.json +64 -0
  17. package/template/resources/icon.png +0 -0
  18. package/template/src/assets/audio/fadeSound.mp3 +0 -0
  19. package/template/src/assets/audio/fadeSound2.mp3 +0 -0
  20. package/template/src/assets/audio/interstellar.mp3 +0 -0
  21. package/template/src/assets/audio/keyboard1.mp3 +0 -0
  22. package/template/src/assets/audio/keyboard2.mp3 +0 -0
  23. package/template/src/assets/audio/keyboard3.mp3 +0 -0
  24. package/template/src/assets/audio/tick_sound.mp3 +0 -0
  25. package/template/src/assets/base.css +67 -0
  26. package/template/src/assets/electron.svg +10 -0
  27. package/template/src/assets/fonts/Geo-Regular.woff +0 -0
  28. package/template/src/assets/fonts/Montserrat-Italic-VariableFont_wght.woff2 +0 -0
  29. package/template/src/assets/fonts/Montserrat-Medium.ttf +0 -0
  30. package/template/src/assets/fonts/Montserrat-Medium.woff +0 -0
  31. package/template/src/assets/fonts/Montserrat-VariableFont_wght.woff2 +0 -0
  32. package/template/src/assets/fonts/glitch.ttf +0 -0
  33. package/template/src/assets/hdri/indoor1.hdr +0 -0
  34. package/template/src/assets/hdri/metro1.hdr +0 -0
  35. package/template/src/assets/hdri/outdoor1.hdr +0 -0
  36. package/template/src/assets/hdri/photo-studio1.hdr +0 -0
  37. package/template/src/assets/hdri/photo-studio2.hdr +0 -0
  38. package/template/src/assets/hdri/photo-studio3.hdr +0 -0
  39. package/template/src/assets/objects/keyboardScene/ibm-keyboard.glb +0 -0
  40. package/template/src/assets/wavy-lines.svg +25 -0
  41. package/template/src/entry.ts +20 -0
  42. package/template/src/example_scenes/alternativesScene.ts +88 -0
  43. package/template/src/example_scenes/dependencyScene.ts +116 -0
  44. package/template/src/example_scenes/fourierMachineScene.ts +108 -0
  45. package/template/src/example_scenes/fourierSeriesScene.ts +678 -0
  46. package/template/src/example_scenes/keyboardScene.ts +447 -0
  47. package/template/src/example_scenes/surfaceScene.ts +88 -0
  48. package/template/src/example_scenes/tutorials/easy1.ts +59 -0
  49. package/template/src/example_scenes/tutorials/easy2.ts +141 -0
  50. package/template/src/example_scenes/tutorials/easy3.ts +133 -0
  51. package/template/src/example_scenes/tutorials/medium1.ts +154 -0
  52. package/template/src/example_scenes/vectorField.ts +209 -0
  53. package/template/src/example_scenes/visulizingFunctions.ts +246 -0
  54. package/template/src/main/index.ts +101 -0
  55. package/template/src/main/rendering.ts +219 -0
  56. package/template/src/main/storage.ts +35 -0
  57. package/template/src/preload/index.d.ts +8 -0
  58. package/template/src/preload/index.ts +36 -0
  59. package/template/src/renderer/index.html +17 -0
  60. package/template/src/renderer/src/App.svelte +130 -0
  61. package/template/src/renderer/src/app.css +24 -0
  62. package/template/src/renderer/src/env.d.ts +2 -0
  63. package/template/src/renderer/src/lib/animation/animations.ts +214 -0
  64. package/template/src/renderer/src/lib/animation/captureCanvas.ts +85 -0
  65. package/template/src/renderer/src/lib/animation/helpers.ts +7 -0
  66. package/template/src/renderer/src/lib/animation/interpolations.ts +155 -0
  67. package/template/src/renderer/src/lib/animation/protocols.ts +79 -0
  68. package/template/src/renderer/src/lib/audio/loader.ts +104 -0
  69. package/template/src/renderer/src/lib/fonts/Roboto_Regular.json +1 -0
  70. package/template/src/renderer/src/lib/fonts/montserrat-medium.json +1 -0
  71. package/template/src/renderer/src/lib/fonts/montserrat.json +1 -0
  72. package/template/src/renderer/src/lib/general/helpers.ts +77 -0
  73. package/template/src/renderer/src/lib/general/onDestory.ts +10 -0
  74. package/template/src/renderer/src/lib/mathHelpers/vectors.ts +18 -0
  75. package/template/src/renderer/src/lib/rendering/bumpMaps/noise.ts +84 -0
  76. package/template/src/renderer/src/lib/rendering/helpers.ts +35 -0
  77. package/template/src/renderer/src/lib/rendering/lighting3d.ts +387 -0
  78. package/template/src/renderer/src/lib/rendering/materials.ts +6 -0
  79. package/template/src/renderer/src/lib/rendering/objects/import.ts +148 -0
  80. package/template/src/renderer/src/lib/rendering/objects2d.ts +489 -0
  81. package/template/src/renderer/src/lib/rendering/objects3d.ts +89 -0
  82. package/template/src/renderer/src/lib/rendering/protocols.ts +21 -0
  83. package/template/src/renderer/src/lib/rendering/setup.ts +71 -0
  84. package/template/src/renderer/src/lib/rendering/svg/drawing.ts +213 -0
  85. package/template/src/renderer/src/lib/rendering/svg/parsing.ts +717 -0
  86. package/template/src/renderer/src/lib/rendering/svg/rastered.ts +42 -0
  87. package/template/src/renderer/src/lib/rendering/svgObjects.ts +1137 -0
  88. package/template/src/renderer/src/lib/scene/helpers.ts +89 -0
  89. package/template/src/renderer/src/lib/scene/sceneClass.ts +648 -0
  90. package/template/src/renderer/src/lib/shaders/background_gradient/frag.glsl +12 -0
  91. package/template/src/renderer/src/lib/shaders/background_gradient/vert.glsl +6 -0
  92. package/template/src/renderer/src/lib/shaders/hdri_blur/frag.glsl +45 -0
  93. package/template/src/renderer/src/lib/shaders/hdri_blur/vert.glsl +5 -0
  94. package/template/src/renderer/src/main.ts +9 -0
  95. package/template/svelte.config.mjs +7 -0
  96. package/template/tsconfig.json +4 -0
  97. package/template/tsconfig.node.json +10 -0
  98. package/template/tsconfig.web.json +32 -0
@@ -0,0 +1,130 @@
1
+ <script lang="ts">
2
+ import './app.css'
3
+ import { generateID, setStateInScene, updateStateInUrl } from './lib/general/helpers'
4
+ import { onDestroy, onMount } from 'svelte'
5
+ import { setGlobalContainerRef, type AnimatedScene } from './lib/scene/sceneClass'
6
+ import { loadFonts } from './lib/rendering/objects2d'
7
+ import { entryScene } from '../../entry'
8
+ import { callAllDestroyFunctions } from './lib/general/onDestory'
9
+
10
+ //const ipcHandle = (): void => window.electron.ipcRenderer.send('ping')
11
+
12
+ const animationWindowID = generateID()
13
+
14
+ let scene: AnimatedScene
15
+
16
+ let isPlayingStateVar = $state(false)
17
+
18
+ let lastSetFrame = 0
19
+
20
+ const maxSliderValue = 10_000
21
+ let urlUpdaterInterval: ReturnType<typeof setInterval>
22
+
23
+ async function handleSliderChange(sliderValue: number) {
24
+ if (scene) {
25
+ const frame = Math.round((sliderValue / maxSliderValue) * (scene.totalSceneTicks - 1))
26
+ if (frame !== lastSetFrame) {
27
+ await scene.jumpToFrameAtIndex(frame)
28
+ lastSetFrame = frame
29
+ }
30
+ }
31
+ }
32
+
33
+ onMount(async () => {
34
+ if (!entryScene) return
35
+ await loadFonts()
36
+ const animationWindow = document.getElementById(animationWindowID)
37
+ const sliderElement = document.getElementById('playerSliderID')
38
+ if (!animationWindow || !sliderElement) return
39
+
40
+ setGlobalContainerRef(animationWindow)
41
+
42
+ scene = entryScene()
43
+
44
+ scene.playEffectFunction = () => {
45
+ ;(sliderElement as any).value =
46
+ (scene.sceneRenderTick / (scene.totalSceneTicks - 1)) * maxSliderValue
47
+ }
48
+ const currentWidth = animationWindow.clientWidth
49
+ animationWindow.style.height = `${currentWidth / scene.getAspectRatio()}px`
50
+
51
+ setStateInScene(scene)
52
+ lastSetFrame = scene.sceneRenderTick
53
+
54
+ urlUpdaterInterval = setInterval(() => {
55
+ updateStateInUrl(scene.sceneRenderTick)
56
+ }, 500)
57
+
58
+ // Add listener to handle window resize events
59
+ window.addEventListener('resize', () => {
60
+ const currentWidth = animationWindow.clientWidth
61
+ animationWindow.style.height = `${currentWidth / scene.getAspectRatio()}px`
62
+ })
63
+
64
+ // ipcRenderer.send('resize-window', { width: 1000, height: 1000 })
65
+ })
66
+
67
+ onDestroy(() => {
68
+ clearInterval(urlUpdaterInterval)
69
+ callAllDestroyFunctions()
70
+ })
71
+ </script>
72
+
73
+ <div class=" flex flex-col p-4">
74
+ <div id={animationWindowID} class="w-full"></div>
75
+ <div class="flex justify-between mt-2 font-bold text-sm">
76
+ <button
77
+ onclick={() => {
78
+ if (scene.isPlaying) {
79
+ scene.pause()
80
+ isPlayingStateVar = false
81
+ } else {
82
+ scene.playSequenceOfAnimation(scene.sceneRenderTick, scene.totalSceneTicks - 1)
83
+ isPlayingStateVar = true
84
+ }
85
+ }}>{isPlayingStateVar ? 'Pause' : 'Play'}</button
86
+ >
87
+ <button
88
+ onclick={() => {
89
+ scene.render()
90
+ }}>Render</button
91
+ >
92
+ </div>
93
+ <div class="w-full px-0 mx-0">
94
+ <input
95
+ type="range"
96
+ min="0"
97
+ max={maxSliderValue}
98
+ oninput={(e: any) => handleSliderChange(Number(e.target.value))}
99
+ class="w-full focus:outline-none"
100
+ id="playerSliderID"
101
+ />
102
+ </div>
103
+ <p id="cameraPositionTextID" class="mt-2 text-xs"></p>
104
+ <p id="cameraRotationTextID" class="mt-2 text-xs"></p>
105
+ </div>
106
+
107
+ <style>
108
+ /* Chrome-only styling for range input */
109
+ input[type='range'] {
110
+ -webkit-appearance: none;
111
+ background: transparent;
112
+ }
113
+
114
+ /* Track style for Chrome */
115
+ input[type='range']::-webkit-slider-runnable-track {
116
+ height: 4px;
117
+ background: #e5e7eb;
118
+ border-radius: 2px;
119
+ }
120
+
121
+ /* Thumb style for Chrome */
122
+ input[type='range']::-webkit-slider-thumb {
123
+ -webkit-appearance: none;
124
+ width: 16px;
125
+ height: 16px;
126
+ border-radius: 50%;
127
+ background: #3b82f6;
128
+ margin-top: -6px; /* Center the thumb on the track */
129
+ }
130
+ </style>
@@ -0,0 +1,24 @@
1
+ @import "tailwindcss";
2
+
3
+
4
+ @font-face {
5
+ font-family: "Montserrat";
6
+ font-style: normal;
7
+ font-weight: 100 900; /* Variable font with a range of weights */
8
+ src: url("./assets/fonts/Montserrat-VariableFont_wght.woff2") format("woff2");
9
+ font-display: swap;
10
+ }
11
+
12
+ @font-face {
13
+ font-family: "Montserrat";
14
+ font-style: italic;
15
+ font-weight: 100 900; /* Variable font with a range of weights for italic style */
16
+ src: url("./assets/fonts/Montserrat-Italic-VariableFont_wght.woff2") format("woff2");
17
+ font-display: swap;
18
+ }
19
+
20
+ html {
21
+ background: #fff;
22
+ font-family: "Montserrat", sans-serif;
23
+ color: #000;
24
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="svelte" />
2
+ /// <reference types="vite/client" />
@@ -0,0 +1,214 @@
1
+ import * as THREE from 'three'
2
+ import { UserAnimation } from './protocols'
3
+ import { easeConstant, easeInOutQuad } from './interpolations'
4
+
5
+ export const setOpacity = <T extends THREE.Object3D>(
6
+ object: T,
7
+ opacity: number,
8
+ enableTransparency: boolean = true
9
+ ) => {
10
+ // Apply opacity to the object and all its children recursively
11
+ object.traverse((child: any) => {
12
+ if (child.material) {
13
+ const materials = child.material instanceof Array ? child.material : [child.material]
14
+
15
+ materials.forEach((mat) => {
16
+ if (enableTransparency && !mat.transparent) {
17
+ mat.transparent = true
18
+ }
19
+ mat.opacity = opacity
20
+ })
21
+ }
22
+ })
23
+
24
+ return object
25
+ }
26
+
27
+ export const setScale = <T extends THREE.Object3D>(
28
+ object: T,
29
+ scale: number | { x?: number; y?: number; z?: number },
30
+ relative: boolean = false
31
+ ): THREE.Object3D => {
32
+ if (typeof scale === 'number') {
33
+ // Uniform scaling with a single number
34
+ if (relative) {
35
+ object.scale.multiplyScalar(scale)
36
+ } else {
37
+ object.scale.set(scale, scale, scale)
38
+ }
39
+ } else {
40
+ // Non-uniform scaling with an object containing x, y, z properties
41
+ const scaleX = scale.x !== undefined ? scale.x : relative ? 1 : object.scale.x
42
+ const scaleY = scale.y !== undefined ? scale.y : relative ? 1 : object.scale.y
43
+ const scaleZ = scale.z !== undefined ? scale.z : relative ? 1 : object.scale.z
44
+
45
+ if (relative) {
46
+ object.scale.x *= scaleX
47
+ object.scale.y *= scaleY
48
+ object.scale.z *= scaleZ
49
+ } else {
50
+ object.scale.set(scaleX, scaleY, scaleZ)
51
+ }
52
+ }
53
+
54
+ return object
55
+ }
56
+
57
+ export const fade = (
58
+ object: THREE.Object3D,
59
+ duration: number = 800,
60
+ fromValue: number = 0,
61
+ toValue: number = 1
62
+ ): UserAnimation => {
63
+ return new UserAnimation(easeInOutQuad(fromValue, toValue, duration), (value) => {
64
+ setOpacity(object, value)
65
+ })
66
+ }
67
+
68
+ export const fadeInTowardsEnd = (object: THREE.Object3D, duration: number = 800): UserAnimation => {
69
+ return new UserAnimation(
70
+ easeConstant(0, duration / 3).concat(easeInOutQuad(0, 1, (2 * duration) / 3)),
71
+ (value) => {
72
+ setOpacity(object, value)
73
+ }
74
+ )
75
+ }
76
+
77
+ export const fadeIn = (object: THREE.Object3D, duration: number = 800) => fade(object, duration)
78
+
79
+ export const fadeOut = (object: THREE.Object3D, duration: number = 800) =>
80
+ fade(object, duration).reverse()
81
+
82
+ export const zoomIn = (
83
+ object: THREE.Object3D,
84
+ duration: number = 800,
85
+ endpoint: number = 1
86
+ ): UserAnimation => {
87
+ return new UserAnimation(easeInOutQuad(0, endpoint, duration), (value) => {
88
+ setScale(object, value)
89
+ })
90
+ }
91
+
92
+ export const zoomOut = (object: THREE.Object3D, duration: number = 800, endpoint: number = 1) =>
93
+ zoomIn(object, duration, endpoint).reverse()
94
+
95
+ export const moveToAnimation = (
96
+ current: THREE.Object3D,
97
+ target: THREE.Vector3,
98
+ duration: number = 800
99
+ ): UserAnimation => {
100
+ // Store initial position and calculate target position
101
+ const startPosition = current.position.clone()
102
+ const targetWorldPosition = target
103
+
104
+ // Convert to current object's parent space if needed
105
+ const targetLocalPosition = new THREE.Vector3()
106
+ if (current.parent) {
107
+ current.parent.worldToLocal(targetLocalPosition.copy(targetWorldPosition))
108
+ } else {
109
+ targetLocalPosition.copy(targetWorldPosition)
110
+ }
111
+
112
+ // Calculate movement delta
113
+ const delta = new THREE.Vector3().subVectors(targetLocalPosition, startPosition)
114
+
115
+ // Create animation with eased interpolation
116
+ return new UserAnimation(easeInOutQuad(0, 1, duration), (progress) => {
117
+ current.position.copy(startPosition.clone().add(delta.clone().multiplyScalar(progress)))
118
+ current.updateMatrixWorld()
119
+ })
120
+ }
121
+
122
+ export const moveCameraAnimation = (
123
+ camera: THREE.OrthographicCamera | THREE.PerspectiveCamera,
124
+ target: THREE.Vector3,
125
+ duration: number = 800
126
+ ): UserAnimation => {
127
+ // Store the target position (we'll capture the start position when animation begins)
128
+ const targetPosition = new THREE.Vector3(
129
+ target.x,
130
+ target.y,
131
+ camera.position.z // Keep the same z position
132
+ )
133
+
134
+ // We'll capture these values when the animation starts
135
+ let startPosition: THREE.Vector3
136
+ let delta: THREE.Vector3
137
+
138
+ // Create animation with eased interpolation
139
+ return new UserAnimation(easeInOutQuad(0, 1, duration), (progress) => {
140
+ // On first frame, capture the current camera position
141
+ if (progress === 0) {
142
+ startPosition = camera.position.clone()
143
+
144
+ // Calculate movement delta (only for x and y)
145
+ delta = new THREE.Vector3(
146
+ targetPosition.x - startPosition.x,
147
+ targetPosition.y - startPosition.y,
148
+ 0 // No change in z
149
+ )
150
+ }
151
+
152
+ camera.position.x = startPosition.x + delta.x * progress
153
+ camera.position.y = startPosition.y + delta.y * progress
154
+ camera.updateMatrixWorld()
155
+ })
156
+ }
157
+
158
+ export const moveRotateCameraAnimation3D = (
159
+ camera: THREE.OrthographicCamera | THREE.PerspectiveCamera,
160
+ startPosition: THREE.Vector3,
161
+ startRotation: THREE.Quaternion,
162
+ positionTarget: THREE.Vector3,
163
+ rotationTarget: THREE.Quaternion,
164
+ duration: number = 800
165
+ ): UserAnimation => {
166
+ const startPosCopy = startPosition.clone()
167
+ const startRotCopy = startRotation.clone()
168
+
169
+ // Store the target position (we'll capture the start position when animation begins)
170
+ const targetPosition = positionTarget.clone()
171
+
172
+ const posDelta = targetPosition.clone().sub(startPosCopy)
173
+
174
+ // Create animation with eased interpolation
175
+ return new UserAnimation(easeInOutQuad(0, 1, duration), (progress) => {
176
+ camera.position.copy(startPosCopy.clone().add(posDelta.clone().multiplyScalar(progress)))
177
+ camera.quaternion.copy(startRotCopy).slerp(rotationTarget, progress)
178
+ })
179
+ }
180
+
181
+ export const moveCameraAnimation3D = (
182
+ camera: THREE.OrthographicCamera | THREE.PerspectiveCamera,
183
+ startPosition: THREE.Vector3,
184
+ positionTarget: THREE.Vector3,
185
+ duration: number = 800
186
+ ): UserAnimation => {
187
+ // Store the target position (we'll capture the start position when animation begins)
188
+ const targetPosition = positionTarget.clone()
189
+
190
+ const posDelta = targetPosition.clone().sub(startPosition)
191
+
192
+ // Create animation with eased interpolation
193
+ return new UserAnimation(easeInOutQuad(0, 1, duration), (progress) => {
194
+ camera.position.copy(startPosition.clone().add(posDelta.clone().multiplyScalar(progress)))
195
+ })
196
+ }
197
+
198
+ export const rotateCamera3D = (
199
+ camera: THREE.OrthographicCamera | THREE.PerspectiveCamera,
200
+ rotationTarget: THREE.Quaternion,
201
+ duration: number = 800
202
+ ): UserAnimation => {
203
+ let startRotation: THREE.Quaternion
204
+
205
+ // Create animation with eased interpolation
206
+ return new UserAnimation(easeInOutQuad(0, 1, duration), (progress) => {
207
+ // On first frame, capture the current camera position
208
+ if (progress === 0) {
209
+ startRotation = camera.quaternion.clone()
210
+ // Calculate movement delta (only for x and y)
211
+ }
212
+ camera.quaternion.copy(startRotation).slerp(rotationTarget, progress)
213
+ })
214
+ }
@@ -0,0 +1,85 @@
1
+ import { renderOutputFps } from '../../../../entry'
2
+ import * as THREE from 'three'
3
+ import { AnimatedScene } from '../scene/sceneClass'
4
+ import { AudioInScene } from '../audio/loader'
5
+
6
+ const fs = require('fs')
7
+ const path = require('path')
8
+
9
+ export const captureCanvasFrame = async (
10
+ currentFrameIndex: number,
11
+ renderName: string,
12
+ threeRenderer: THREE.WebGLRenderer
13
+ ) => {
14
+ try {
15
+ const dirPath = path.join('image_renders', `render_${renderName}`)
16
+
17
+ // Create directory if needed
18
+ if (!fs.existsSync(dirPath)) {
19
+ fs.mkdirSync(dirPath, { recursive: true })
20
+ }
21
+
22
+ // Generate filename with .rgb extension
23
+ const paddedIndex = currentFrameIndex.toString().padStart(5, '0')
24
+ const filename = `frame_${paddedIndex}.jpeg`
25
+ const filePath = path.join(dirPath, filename)
26
+
27
+ // Get WebGL context and pixel data
28
+ const canvas = threeRenderer.domElement
29
+
30
+ // Use the canvas.toBlob method to capture a JPEG image.
31
+ // Note: The quality parameter is between 0 and 1.
32
+ const blob = await new Promise((resolve) => {
33
+ canvas.toBlob(resolve, 'image/jpeg', 0.85)
34
+ })
35
+
36
+ // Convert the blob to an ArrayBuffer then to a Node.js Buffer.
37
+ const arrayBuffer = await (blob as any).arrayBuffer()
38
+ const buffer = Buffer.from(arrayBuffer)
39
+
40
+ await fs.promises.writeFile(filePath, buffer)
41
+
42
+ console.log(`Saved RAW frame ${currentFrameIndex} to ${filePath}`)
43
+ return filePath
44
+ } catch (error) {
45
+ console.error('Error saving canvas frame:', error)
46
+ throw error
47
+ }
48
+ }
49
+ export const triggerEncoder = async (
50
+ width: number,
51
+ height: number,
52
+ renderingAudioGather: AudioInScene[]
53
+ ) => {
54
+ try {
55
+ // Call the exposed function via the 'api' object.
56
+ const response = await (window as any).api.startVideoRender({
57
+ fps: renderOutputFps(),
58
+ width,
59
+ height,
60
+ renderingAudioGather
61
+ })
62
+ if (response.success) {
63
+ console.log('Video rendered successfully at:', response.outputFile)
64
+ // You can update the UI to show the output file path or provide a link to view it
65
+ } else {
66
+ console.error('Video render failed:', response.error)
67
+ }
68
+ } catch (error) {
69
+ console.error('Error calling render:', error)
70
+ }
71
+ /*
72
+
73
+ exec(
74
+ './rust-media/target/release/rust-media ' + Math.round(renderOutputFps()).toString(),
75
+ (error, stdout, stderr) => {
76
+ if (error) {
77
+ console.error(`Error executing command: ${error}`)
78
+ return
79
+ }
80
+ console.log(`stdout: ${stdout}`)
81
+ console.error(`stderr: ${stderr}`)
82
+ }
83
+ )
84
+ */
85
+ }
@@ -0,0 +1,7 @@
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)
@@ -0,0 +1,155 @@
1
+ import { millisToTicks } from './helpers'
2
+
3
+ export const concatInterpols = (...interpolations: number[][]) => {
4
+ return interpolations.reduce((acc, curr) => acc.concat(curr), [])
5
+ }
6
+
7
+ export const easeConstant = (start: number, duration: number): number[] => {
8
+ const numFrames = millisToTicks(duration)
9
+ return new Array(numFrames).fill(start)
10
+ }
11
+
12
+ export const easeLinear = (start: number, end: number, duration: number): number[] => {
13
+ const numFrames = millisToTicks(duration)
14
+ const values: number[] = []
15
+ for (let i = 0; i < numFrames; i++) {
16
+ const t = i / (numFrames - 1)
17
+ values.push(start + (end - start) * t)
18
+ }
19
+ return values
20
+ }
21
+
22
+ export const easeInOutQuad = (start: number, end: number, duration: number): number[] => {
23
+ const numFrames = millisToTicks(duration)
24
+ const values: number[] = []
25
+ for (let i = 0; i < numFrames; i++) {
26
+ // Normalize current step to a value between 0 and 1.
27
+ const t = i / (numFrames - 1)
28
+ // Apply the quadratic easeInOut formula.
29
+ let eased: number
30
+ if (t < 0.5) {
31
+ eased = 2 * t * t
32
+ } else {
33
+ eased = -1 + (4 - 2 * t) * t
34
+ }
35
+ // Interpolate between start and end.
36
+ const value = start + (end - start) * eased
37
+ values.push(value)
38
+ }
39
+ return values
40
+ }
41
+
42
+ export const rubberband = (start: number, end: number, duration: number): number[] => {
43
+ const numFrames = millisToTicks(duration)
44
+ const values: number[] = []
45
+ const overshoot = 1.70158 // Controls overshoot amount (1.70158 = 10% overshoot)
46
+
47
+ for (let i = 0; i < numFrames; i++) {
48
+ const t = i / (numFrames - 1)
49
+ let eased: number
50
+
51
+ if (t === 0) {
52
+ eased = 0
53
+ } else if (t === 1) {
54
+ eased = 1
55
+ } else {
56
+ eased = 1 + (overshoot + 1) * Math.pow(t - 1, 3) + overshoot * Math.pow(t - 1, 2)
57
+ }
58
+
59
+ const value = start + (end - start) * eased
60
+ values.push(value)
61
+ }
62
+ return values
63
+ }
64
+
65
+ /**
66
+ * Interpolates a vector to a new size using linear interpolation.
67
+ *
68
+ * @param vector - The original array of numbers.
69
+ * @param newSize - The desired size of the new vector (should be >= original size).
70
+ * @returns A new array of numbers with the interpolated values.
71
+ */
72
+ export const interpolateVector = (vector: number[], newSize: number): number[] => {
73
+ const originalSize = vector.length
74
+ if (newSize < originalSize) {
75
+ throw new Error('New size must be greater than or equal to the original size.')
76
+ }
77
+
78
+ const result: number[] = []
79
+ // Calculate the step in the original vector for each new element.
80
+ const step = (originalSize - 1) / (newSize - 1)
81
+
82
+ for (let i = 0; i < newSize; i++) {
83
+ // Determine the position in the original array
84
+ const pos = i * step
85
+ const index = Math.floor(pos)
86
+ const remainder = pos - index
87
+
88
+ // If pos falls exactly on an original index, just use it.
89
+ if (remainder === 0 || index === originalSize - 1) {
90
+ result.push(vector[index])
91
+ } else {
92
+ // Linear interpolation between vector[index] and vector[index + 1]
93
+ const interpolatedValue = vector[index] * (1 - remainder) + vector[index + 1] * remainder
94
+ result.push(interpolatedValue)
95
+ }
96
+ }
97
+
98
+ return result
99
+ }
100
+
101
+ export const compressVector = (vector: number[], newSize: number): number[] => {
102
+ const originalSize = vector.length
103
+ if (newSize > originalSize) {
104
+ throw new Error('New size must be smaller than or equal to the original size.')
105
+ }
106
+ if (newSize === originalSize) return [...vector]
107
+
108
+ const result: number[] = []
109
+ const blockSize = originalSize / newSize
110
+
111
+ for (let i = 0; i < newSize; i++) {
112
+ const start = i * blockSize
113
+ const end = start + blockSize
114
+
115
+ // Collect all values overlapping with the current block
116
+ let sum = 0
117
+ let count = 0
118
+ for (let j = Math.floor(start); j < Math.ceil(end); j++) {
119
+ // Weight by overlap fraction (handles partial overlaps)
120
+ const overlapStart = Math.max(start, j)
121
+ const overlapEnd = Math.min(end, j + 1)
122
+ const weight = overlapEnd - overlapStart
123
+
124
+ sum += vector[j] * weight
125
+ count += weight
126
+ }
127
+ result.push(sum / count)
128
+ }
129
+
130
+ return result
131
+ }
132
+
133
+ /**
134
+ * Applies random noise in place to each element of a number vector.
135
+ *
136
+ * @param vector - The array of numbers to be modified.
137
+ * @param noiseLevel - The maximum absolute noise to add to each element.
138
+ */
139
+ export const noiseOnInterpolation = (vector: number[], noiseLevel: number): void => {
140
+ for (let i = 0; i < vector.length; i++) {
141
+ // Generate noise in the range [-noiseLevel, noiseLevel]
142
+ const noise = (Math.random() * 2 - 1) * noiseLevel
143
+ vector[i] += noise
144
+ }
145
+ }
146
+
147
+ export const posXSigmoid = (x: number): number => {
148
+ x = Math.abs(x)
149
+ if (x < 0) return 0
150
+
151
+ // Standard sigmoid is 1/(1+e^(-x))
152
+ // At x=0, sigmoid(0) = 0.5
153
+ // Shifting down by 0.5 and scaling by 2 gives us 0 at x=0
154
+ return 2 * (1 / (1 + Math.exp(-x)) - 0.5)
155
+ }
@@ -0,0 +1,79 @@
1
+ import { compressVector, interpolateVector, noiseOnInterpolation } from './interpolations'
2
+
3
+ export interface DefinedAnimation {
4
+ interpolation: number[]
5
+ updater: UpdaterFunction
6
+ }
7
+
8
+ export class UserAnimation implements DefinedAnimation {
9
+ interpolation: number[]
10
+ updater: UpdaterFunction
11
+
12
+ constructor(interpolation: number[], updater: UpdaterFunction) {
13
+ this.interpolation = interpolation
14
+ this.updater = updater
15
+ }
16
+
17
+ addNoise(scale: number = 1): this {
18
+ noiseOnInterpolation(this.interpolation, scale)
19
+ return this
20
+ }
21
+
22
+ scaleLength(scale: number): this {
23
+ if (scale === 1) return this
24
+ else if (scale > 1) {
25
+ this.interpolation = interpolateVector(
26
+ this.interpolation,
27
+ Math.round(this.interpolation.length * scale)
28
+ )
29
+ } else {
30
+ this.interpolation = compressVector(
31
+ this.interpolation,
32
+ Math.round(this.interpolation.length * scale)
33
+ )
34
+ }
35
+ return this
36
+ }
37
+
38
+ sum(add: number[]): this {
39
+ const maxLength = Math.max(this.interpolation.length, add.length)
40
+ const newInterpolation: number[] = []
41
+
42
+ for (let i = 0; i < maxLength; i++) {
43
+ const a = this.interpolation[i] ?? 0 // Use 0 if undefined
44
+ const b = add[i] ?? 0 // Use 0 if undefined
45
+ newInterpolation[i] = a + b
46
+ }
47
+
48
+ this.interpolation = newInterpolation
49
+ return this
50
+ }
51
+
52
+ reverse(): this {
53
+ this.interpolation.reverse()
54
+ return this
55
+ }
56
+
57
+ copy(): UserAnimation {
58
+ return new UserAnimation([...this.interpolation], this.updater)
59
+ }
60
+ }
61
+
62
+ export const createAnimNamed = (animation: DefinedAnimation): UserAnimation => {
63
+ return new UserAnimation(animation.interpolation, animation.updater)
64
+ }
65
+
66
+ export const createAnim = (interpolation: number[], updater: UpdaterFunction): UserAnimation => {
67
+ return new UserAnimation(interpolation, updater)
68
+ }
69
+
70
+ export interface InternalAnimation {
71
+ startTick: number
72
+ endTick: number
73
+ interpolation: number[]
74
+ updater: UpdaterFunction
75
+ }
76
+
77
+ export type DependencyUpdater = (sceneTick: number, time: number) => any
78
+
79
+ export type UpdaterFunction = (interpolation: number, sceneTick: number, isLast: boolean) => any