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.
- package/bin/index.js +3 -0
- package/package.json +37 -0
- package/src/cli.js +100 -0
- package/template/.editorconfig +9 -0
- package/template/.prettierignore +6 -0
- package/template/.prettierrc.yaml +10 -0
- package/template/_gitignore +10 -0
- package/template/build/entitlements.mac.plist +12 -0
- package/template/build/icon.icns +0 -0
- package/template/build/icon.ico +0 -0
- package/template/build/icon.png +0 -0
- package/template/electron-builder.yml +43 -0
- package/template/electron.vite.config.ts +50 -0
- package/template/eslint.config.mjs +24 -0
- package/template/package-lock.json +10299 -0
- package/template/package.json +64 -0
- package/template/resources/icon.png +0 -0
- package/template/src/assets/audio/fadeSound.mp3 +0 -0
- package/template/src/assets/audio/fadeSound2.mp3 +0 -0
- package/template/src/assets/audio/interstellar.mp3 +0 -0
- package/template/src/assets/audio/keyboard1.mp3 +0 -0
- package/template/src/assets/audio/keyboard2.mp3 +0 -0
- package/template/src/assets/audio/keyboard3.mp3 +0 -0
- package/template/src/assets/audio/tick_sound.mp3 +0 -0
- package/template/src/assets/base.css +67 -0
- package/template/src/assets/electron.svg +10 -0
- package/template/src/assets/fonts/Geo-Regular.woff +0 -0
- package/template/src/assets/fonts/Montserrat-Italic-VariableFont_wght.woff2 +0 -0
- package/template/src/assets/fonts/Montserrat-Medium.ttf +0 -0
- package/template/src/assets/fonts/Montserrat-Medium.woff +0 -0
- package/template/src/assets/fonts/Montserrat-VariableFont_wght.woff2 +0 -0
- package/template/src/assets/fonts/glitch.ttf +0 -0
- package/template/src/assets/hdri/indoor1.hdr +0 -0
- package/template/src/assets/hdri/metro1.hdr +0 -0
- package/template/src/assets/hdri/outdoor1.hdr +0 -0
- package/template/src/assets/hdri/photo-studio1.hdr +0 -0
- package/template/src/assets/hdri/photo-studio2.hdr +0 -0
- package/template/src/assets/hdri/photo-studio3.hdr +0 -0
- package/template/src/assets/objects/keyboardScene/ibm-keyboard.glb +0 -0
- package/template/src/assets/wavy-lines.svg +25 -0
- package/template/src/entry.ts +20 -0
- package/template/src/example_scenes/alternativesScene.ts +88 -0
- package/template/src/example_scenes/dependencyScene.ts +116 -0
- package/template/src/example_scenes/fourierMachineScene.ts +108 -0
- package/template/src/example_scenes/fourierSeriesScene.ts +678 -0
- package/template/src/example_scenes/keyboardScene.ts +447 -0
- package/template/src/example_scenes/surfaceScene.ts +88 -0
- package/template/src/example_scenes/tutorials/easy1.ts +59 -0
- package/template/src/example_scenes/tutorials/easy2.ts +141 -0
- package/template/src/example_scenes/tutorials/easy3.ts +133 -0
- package/template/src/example_scenes/tutorials/medium1.ts +154 -0
- package/template/src/example_scenes/vectorField.ts +209 -0
- package/template/src/example_scenes/visulizingFunctions.ts +246 -0
- package/template/src/main/index.ts +101 -0
- package/template/src/main/rendering.ts +219 -0
- package/template/src/main/storage.ts +35 -0
- package/template/src/preload/index.d.ts +8 -0
- package/template/src/preload/index.ts +36 -0
- package/template/src/renderer/index.html +17 -0
- package/template/src/renderer/src/App.svelte +130 -0
- package/template/src/renderer/src/app.css +24 -0
- package/template/src/renderer/src/env.d.ts +2 -0
- package/template/src/renderer/src/lib/animation/animations.ts +214 -0
- package/template/src/renderer/src/lib/animation/captureCanvas.ts +85 -0
- package/template/src/renderer/src/lib/animation/helpers.ts +7 -0
- package/template/src/renderer/src/lib/animation/interpolations.ts +155 -0
- package/template/src/renderer/src/lib/animation/protocols.ts +79 -0
- package/template/src/renderer/src/lib/audio/loader.ts +104 -0
- package/template/src/renderer/src/lib/fonts/Roboto_Regular.json +1 -0
- package/template/src/renderer/src/lib/fonts/montserrat-medium.json +1 -0
- package/template/src/renderer/src/lib/fonts/montserrat.json +1 -0
- package/template/src/renderer/src/lib/general/helpers.ts +77 -0
- package/template/src/renderer/src/lib/general/onDestory.ts +10 -0
- package/template/src/renderer/src/lib/mathHelpers/vectors.ts +18 -0
- package/template/src/renderer/src/lib/rendering/bumpMaps/noise.ts +84 -0
- package/template/src/renderer/src/lib/rendering/helpers.ts +35 -0
- package/template/src/renderer/src/lib/rendering/lighting3d.ts +387 -0
- package/template/src/renderer/src/lib/rendering/materials.ts +6 -0
- package/template/src/renderer/src/lib/rendering/objects/import.ts +148 -0
- package/template/src/renderer/src/lib/rendering/objects2d.ts +489 -0
- package/template/src/renderer/src/lib/rendering/objects3d.ts +89 -0
- package/template/src/renderer/src/lib/rendering/protocols.ts +21 -0
- package/template/src/renderer/src/lib/rendering/setup.ts +71 -0
- package/template/src/renderer/src/lib/rendering/svg/drawing.ts +213 -0
- package/template/src/renderer/src/lib/rendering/svg/parsing.ts +717 -0
- package/template/src/renderer/src/lib/rendering/svg/rastered.ts +42 -0
- package/template/src/renderer/src/lib/rendering/svgObjects.ts +1137 -0
- package/template/src/renderer/src/lib/scene/helpers.ts +89 -0
- package/template/src/renderer/src/lib/scene/sceneClass.ts +648 -0
- package/template/src/renderer/src/lib/shaders/background_gradient/frag.glsl +12 -0
- package/template/src/renderer/src/lib/shaders/background_gradient/vert.glsl +6 -0
- package/template/src/renderer/src/lib/shaders/hdri_blur/frag.glsl +45 -0
- package/template/src/renderer/src/lib/shaders/hdri_blur/vert.glsl +5 -0
- package/template/src/renderer/src/main.ts +9 -0
- package/template/svelte.config.mjs +7 -0
- package/template/tsconfig.json +4 -0
- package/template/tsconfig.node.json +10 -0
- 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,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
|