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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { spawnSync } from 'child_process'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
4
5
|
|
|
5
6
|
export interface AudioInScene {
|
|
6
7
|
audioPath: string
|
|
@@ -136,28 +137,42 @@ export function renderVideo(options: RenderOptions): Promise<string> {
|
|
|
136
137
|
}
|
|
137
138
|
})
|
|
138
139
|
}
|
|
139
|
-
/*
|
|
140
|
-
function generateFFmpegAudioCommand(
|
|
141
|
-
options: RenderOptions,
|
|
142
|
-
outputFolder: string,
|
|
143
|
-
id: string
|
|
144
|
-
): string {
|
|
145
|
-
const inputs: string[] = []
|
|
146
|
-
const filterChains: string[] = []
|
|
147
|
-
const amixInputs: string[] = []
|
|
148
|
-
|
|
149
|
-
options.renderingAudioGather.forEach((audio, index) => {
|
|
150
|
-
const startTime = audio.atFrame / options.fps
|
|
151
|
-
inputs.push(`-i ./src/renderer${audio.audioPath}`)
|
|
152
|
-
filterChains.push(`[${index}:a]asetpts=PTS+${startTime}/TB,volume=${audio.volume}[a${index}]`)
|
|
153
|
-
amixInputs.push(`[a${index}]`)
|
|
154
|
-
})
|
|
155
140
|
|
|
156
|
-
const amixFilter = `${amixInputs.join('')}amix=inputs=${amixInputs.length}:duration=longest[aout]`
|
|
157
|
-
const filterComplex = [...filterChains, amixFilter].join('; ')
|
|
158
141
|
|
|
159
|
-
|
|
160
|
-
|
|
142
|
+
function toFsPath(p: string): string {
|
|
143
|
+
if (!p) throw new Error('empty path')
|
|
144
|
+
|
|
145
|
+
// Handle Vite dev absolute path
|
|
146
|
+
if (p.startsWith('/@fs/')) {
|
|
147
|
+
// Keep the leading slash before "Users"
|
|
148
|
+
return decodeURIComponent(p.slice(4)) // results in "/Users/…"
|
|
149
|
+
// Alternatively:
|
|
150
|
+
// return decodeURIComponent(p.replace(/^\/@fs/, ''))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 2) file:// URL
|
|
154
|
+
try {
|
|
155
|
+
const u = new URL(p)
|
|
156
|
+
if (u.protocol === 'file:') return fileURLToPath(u)
|
|
157
|
+
} catch {/* not a URL */}
|
|
158
|
+
|
|
159
|
+
// 3) already absolute on disk
|
|
160
|
+
if (path.isAbsolute(p)) return p
|
|
161
|
+
|
|
162
|
+
// 4) fallback: try typical asset roots
|
|
163
|
+
const cleaned = p.replace(/^\/?assets\//, '') // allow "assets/foo.mp3" or "/assets/foo.mp3"
|
|
164
|
+
const guesses = [
|
|
165
|
+
path.join(process.cwd(), 'src', 'renderer', 'assets', cleaned), // dev
|
|
166
|
+
path.join(process.resourcesPath, 'assets', cleaned), // prod packaged
|
|
167
|
+
path.join(process.cwd(), cleaned) // last resort
|
|
168
|
+
]
|
|
169
|
+
for (const g of guesses) {
|
|
170
|
+
if (fs.existsSync(g)) return g
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// final fallback: return as-is (FFmpeg will error, but at least we see the attempted path)
|
|
174
|
+
return p
|
|
175
|
+
}
|
|
161
176
|
|
|
162
177
|
function buildAudioMixCommand(renderOptions: RenderOptions, outputFolder: string, id: string) {
|
|
163
178
|
const { fps, renderingAudioGather } = renderOptions
|
|
@@ -166,7 +181,9 @@ function buildAudioMixCommand(renderOptions: RenderOptions, outputFolder: string
|
|
|
166
181
|
let inputIndexes: string[] = []
|
|
167
182
|
|
|
168
183
|
renderingAudioGather.forEach((audio, index) => {
|
|
169
|
-
|
|
184
|
+
// Prefer an already-provided absolute fsPath (if you later add it); else normalize here
|
|
185
|
+
const inputPath = (audio as any).fsPath ?? toFsPath(audio.audioPath)
|
|
186
|
+
inputs.push(`-i "${inputPath}"`)
|
|
170
187
|
const delayMs = Math.floor((audio.atFrame / fps) * 1000)
|
|
171
188
|
// adelay syntax: "adelay=delay_in_ms|delay_in_ms"
|
|
172
189
|
filters.push(`[${index}:a]adelay=${delayMs}|${delayMs},volume=${audio.volume}[a${index}]`)
|
|
@@ -2,8 +2,22 @@ import { contextBridge, ipcRenderer } from 'electron'
|
|
|
2
2
|
import { electronAPI } from '@electron-toolkit/preload'
|
|
3
3
|
import { RenderOptions } from '../main/rendering'
|
|
4
4
|
|
|
5
|
+
|
|
6
|
+
// ---- NEW helpers for event subscription
|
|
7
|
+
function onDisplayHzChanged(cb: (hz: number) => void) {
|
|
8
|
+
const channel = 'display-hz-changed'
|
|
9
|
+
const listener = (_: unknown, hz: number) => cb(hz)
|
|
10
|
+
ipcRenderer.on(channel, listener)
|
|
11
|
+
// return an unsubscribe fn
|
|
12
|
+
return () => ipcRenderer.removeListener(channel, listener)
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
const customAPI = {
|
|
6
|
-
startVideoRender: (options: RenderOptions) => ipcRenderer.invoke('start-video-render', options)
|
|
16
|
+
startVideoRender: (options: RenderOptions) => ipcRenderer.invoke('start-video-render', options),
|
|
17
|
+
|
|
18
|
+
getDisplayHz: (): Promise<number> => ipcRenderer.invoke('get-display-hz'),
|
|
19
|
+
|
|
20
|
+
onDisplayHzChanged
|
|
7
21
|
}
|
|
8
22
|
|
|
9
23
|
// Custom APIs for renderer
|
|
@@ -2,16 +2,45 @@
|
|
|
2
2
|
import './app.css'
|
|
3
3
|
import { generateID, setStateInScene, updateStateInUrl } from './lib/general/helpers'
|
|
4
4
|
import { onDestroy, onMount } from 'svelte'
|
|
5
|
-
import { setGlobalContainerRef, type AnimatedScene } from './lib/scene/sceneClass'
|
|
5
|
+
import { hotreloadNameLookup, screenFPS, setGlobalContainerRef, type AnimatedScene } from './lib/scene/sceneClass'
|
|
6
6
|
import { loadFonts } from './lib/rendering/objects2d'
|
|
7
|
-
import { entryScene } from '../../entry'
|
|
7
|
+
import { animationFPSDivider, entryScene, renderSkip } from '../../entry'
|
|
8
8
|
import { callAllDestroyFunctions } from './lib/general/onDestory'
|
|
9
|
+
import rotateIcon from "./application_assets/360.svg"
|
|
10
|
+
import moveIcon from "./application_assets/move.svg"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
let frameValueElement: HTMLParagraphElement
|
|
14
|
+
let timeValueElement: HTMLParagraphElement
|
|
15
|
+
let sliderElement: HTMLInputElement
|
|
16
|
+
|
|
17
|
+
const TEXT_FRAME_MS_LIMIT = 0.90*1000 / 30; // A little lower to avoid skipping frames when timing is unfourtunate
|
|
18
|
+
const SLIDER_FRAME_MS_LIMIT = 0.90*1000 / 60; // A little lower to avoid skipping frames when timing is unfourtunate
|
|
19
|
+
|
|
20
|
+
let lastTextUpdate = 0;
|
|
21
|
+
let lastSliderUpdate = 0;
|
|
22
|
+
|
|
23
|
+
let screenRefreshRate = $state(0)
|
|
24
|
+
let isRendering = $state(false)
|
|
25
|
+
|
|
26
|
+
let isScrubbing = false
|
|
27
|
+
let wasPlayingBeforeScrub = false
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
function formatMs(ms: number) {
|
|
31
|
+
const sign = ms < 0 ? '-' : ''
|
|
32
|
+
ms = Math.abs(ms)
|
|
33
|
+
const minutes = Math.floor(ms / 60000)
|
|
34
|
+
const seconds = Math.floor((ms % 60000) / 1000)
|
|
35
|
+
const millis = Math.floor(ms % 1000)
|
|
36
|
+
return `${sign}${String(minutes).padStart(2,'0')}:${String(seconds).padStart(2,'0')}.${String(millis).padStart(3,'0')}`
|
|
37
|
+
}
|
|
9
38
|
|
|
10
|
-
//const ipcHandle = (): void => window.electron.ipcRenderer.send('ping')
|
|
11
39
|
|
|
12
40
|
const animationWindowID = generateID()
|
|
13
41
|
|
|
14
42
|
let scene: AnimatedScene
|
|
43
|
+
let hasInitScene = $state(false)
|
|
15
44
|
|
|
16
45
|
let isPlayingStateVar = $state(false)
|
|
17
46
|
|
|
@@ -25,29 +54,70 @@
|
|
|
25
54
|
const frame = Math.round((sliderValue / maxSliderValue) * (scene.totalSceneTicks - 1))
|
|
26
55
|
if (frame !== lastSetFrame) {
|
|
27
56
|
await scene.jumpToFrameAtIndex(frame)
|
|
57
|
+
updateUIImmediate();
|
|
28
58
|
lastSetFrame = frame
|
|
29
59
|
}
|
|
30
60
|
}
|
|
31
61
|
}
|
|
32
62
|
|
|
63
|
+
function updateSliderOnly() {
|
|
64
|
+
if (!scene || !sliderElement) return
|
|
65
|
+
if (isScrubbing) return // <-- single guard
|
|
66
|
+
const denom = Math.max(1, (scene.totalSceneTicks - 1))
|
|
67
|
+
;(sliderElement as any).value = (scene.sceneRenderTick / denom) * maxSliderValue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function updateTextsOnly() {
|
|
71
|
+
if (!scene) return;
|
|
72
|
+
if (frameValueElement) frameValueElement.textContent = `Frame: ${scene.sceneRenderTick}`;
|
|
73
|
+
if (timeValueElement) timeValueElement.textContent = `Time: ${formatMs(scene.getCurrentTimeMs())}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function updateUIImmediate() {
|
|
77
|
+
updateSliderOnly();
|
|
78
|
+
updateTextsOnly();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
function maybeUpdateUI() {
|
|
83
|
+
const now = performance.now();
|
|
84
|
+
|
|
85
|
+
// ~60 Hz slider
|
|
86
|
+
if (now - lastSliderUpdate >= SLIDER_FRAME_MS_LIMIT) {
|
|
87
|
+
lastSliderUpdate = now;
|
|
88
|
+
updateSliderOnly();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ~30 Hz texts
|
|
92
|
+
if (now - lastTextUpdate >= TEXT_FRAME_MS_LIMIT) {
|
|
93
|
+
lastTextUpdate = now;
|
|
94
|
+
updateTextsOnly();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
33
98
|
onMount(async () => {
|
|
34
99
|
if (!entryScene) return
|
|
35
100
|
await loadFonts()
|
|
36
101
|
const animationWindow = document.getElementById(animationWindowID)
|
|
37
|
-
|
|
102
|
+
|
|
38
103
|
if (!animationWindow || !sliderElement) return
|
|
39
104
|
|
|
40
105
|
setGlobalContainerRef(animationWindow)
|
|
41
106
|
|
|
42
107
|
scene = entryScene()
|
|
108
|
+
hasInitScene = true
|
|
43
109
|
|
|
44
110
|
scene.playEffectFunction = () => {
|
|
45
|
-
|
|
46
|
-
(scene.sceneRenderTick / (scene.totalSceneTicks - 1)) * maxSliderValue
|
|
111
|
+
maybeUpdateUI();
|
|
47
112
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
113
|
+
scene.renderingEventFunction = (isStart) => {
|
|
114
|
+
isRendering = isStart
|
|
115
|
+
if (!isStart) {
|
|
116
|
+
// render just finished; force UI to reflect the reset frame
|
|
117
|
+
updateUIImmediate()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
51
121
|
setStateInScene(scene)
|
|
52
122
|
lastSetFrame = scene.sceneRenderTick
|
|
53
123
|
|
|
@@ -55,11 +125,9 @@
|
|
|
55
125
|
updateStateInUrl(scene.sceneRenderTick)
|
|
56
126
|
}, 500)
|
|
57
127
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
animationWindow.style.height = `${currentWidth / scene.getAspectRatio()}px`
|
|
62
|
-
})
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
screenRefreshRate = screenFPS
|
|
63
131
|
|
|
64
132
|
// ipcRenderer.send('resize-window', { width: 1000, height: 1000 })
|
|
65
133
|
})
|
|
@@ -68,23 +136,67 @@
|
|
|
68
136
|
clearInterval(urlUpdaterInterval)
|
|
69
137
|
callAllDestroyFunctions()
|
|
70
138
|
})
|
|
139
|
+
|
|
140
|
+
function fmt(n: number) {
|
|
141
|
+
return Number(n).toPrecision(7);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function cameraPositionCode() {
|
|
145
|
+
const p = scene.camera.position;
|
|
146
|
+
return `scene.camera.position.set(
|
|
147
|
+
${fmt(p.x)},
|
|
148
|
+
${fmt(p.y)},
|
|
149
|
+
${fmt(p.z)}
|
|
150
|
+
);`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function cameraRotationCode() {
|
|
154
|
+
const q = scene.camera.quaternion;
|
|
155
|
+
return `scene.camera.quaternion.set(
|
|
156
|
+
${fmt(q.x)},
|
|
157
|
+
${fmt(q.y)},
|
|
158
|
+
${fmt(q.z)},
|
|
159
|
+
${fmt(q.w)}
|
|
160
|
+
);`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function copyToClipboard(text: string): Promise<void> {
|
|
164
|
+
await navigator.clipboard.writeText(text);
|
|
165
|
+
}
|
|
71
166
|
</script>
|
|
72
167
|
|
|
73
|
-
<div class=" flex flex-col p-
|
|
74
|
-
<div id={animationWindowID} class="w-full"></div>
|
|
75
|
-
|
|
168
|
+
<div class=" flex flex-col p-2">
|
|
169
|
+
<div id={animationWindowID} class="w-full rounded-sm overflow-clip"></div>
|
|
170
|
+
{#if isRendering}
|
|
171
|
+
<p class="text-[17px] self-center p-6 pb-1">Do <strong>not save</strong> code during rendering</p>
|
|
172
|
+
<p class="text-xs self-center pt-0 p-2">The viewer might hot reload and affect the result</p>
|
|
173
|
+
{/if}
|
|
174
|
+
|
|
175
|
+
<div class="flex justify-between mt-2 font-bold text-sm items-center">
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
76
179
|
<button
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
180
|
+
class="w-[70px] text-xs cursor-pointer bg-black/5 rounded-full p-1 hover:bg-black/10 transition"
|
|
181
|
+
onclick={() => {
|
|
182
|
+
if (scene.isPlaying) {
|
|
183
|
+
scene.pause()
|
|
184
|
+
updateUIImmediate();
|
|
185
|
+
isPlayingStateVar = false
|
|
186
|
+
} else {
|
|
187
|
+
scene.playSequenceOfAnimation(scene.sceneRenderTick, scene.totalSceneTicks - 1)
|
|
188
|
+
isPlayingStateVar = true
|
|
189
|
+
}
|
|
190
|
+
}}>{isPlayingStateVar ? 'Pause' : 'Play'}</button
|
|
191
|
+
>
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
<div class="flex ">
|
|
195
|
+
<p bind:this={frameValueElement} class="font-normal text-[0.7rem] leading-none mr-2 w-[83px]">Frame:</p>
|
|
196
|
+
<p bind:this={timeValueElement} class="font-normal text-[0.7rem] leading-none w-[93px] ">Time:</p>
|
|
197
|
+
</div>
|
|
87
198
|
<button
|
|
199
|
+
class="w-[70px] text-xs cursor-pointer bg-black/5 rounded-full p-1 hover:bg-black/10 transition"
|
|
88
200
|
onclick={() => {
|
|
89
201
|
scene.render()
|
|
90
202
|
}}>Render</button
|
|
@@ -92,16 +204,77 @@
|
|
|
92
204
|
</div>
|
|
93
205
|
<div class="w-full px-0 mx-0">
|
|
94
206
|
<input
|
|
207
|
+
bind:this={sliderElement}
|
|
95
208
|
type="range"
|
|
96
209
|
min="0"
|
|
97
210
|
max={maxSliderValue}
|
|
98
|
-
|
|
211
|
+
|
|
99
212
|
class="w-full focus:outline-none"
|
|
100
|
-
|
|
213
|
+
onpointerdown={() => {
|
|
214
|
+
if (!scene) return
|
|
215
|
+
isScrubbing = true
|
|
216
|
+
wasPlayingBeforeScrub = scene.isPlaying
|
|
217
|
+
if (scene.isPlaying) {
|
|
218
|
+
scene.pause() // silences audio and stops RAF
|
|
219
|
+
isPlayingStateVar = false
|
|
220
|
+
}
|
|
221
|
+
}}
|
|
222
|
+
oninput={(e: any) => {
|
|
223
|
+
// while scrubbing: jump visuals quietly; when not scrubbing, behaves like before
|
|
224
|
+
const v = Number(e.target.value)
|
|
225
|
+
handleSliderChange(v)
|
|
226
|
+
}}
|
|
227
|
+
onpointerup={(e: any) => {
|
|
228
|
+
if (!scene) return
|
|
229
|
+
isScrubbing = false
|
|
230
|
+
const v = Number((e.target as HTMLInputElement).value)
|
|
231
|
+
// ensure we’re at the dropped frame
|
|
232
|
+
handleSliderChange(v)
|
|
233
|
+
if (wasPlayingBeforeScrub) {
|
|
234
|
+
// resume cleanly from here
|
|
235
|
+
scene.playSequenceOfAnimation(scene.sceneRenderTick, scene.totalSceneTicks - 1)
|
|
236
|
+
isPlayingStateVar = true
|
|
237
|
+
}
|
|
238
|
+
}}
|
|
101
239
|
/>
|
|
102
240
|
</div>
|
|
241
|
+
|
|
242
|
+
<div class="h-4"></div>
|
|
243
|
+
{#if hasInitScene && scene}
|
|
244
|
+
|
|
245
|
+
<p class="font-bold text-sm">Helpers</p>
|
|
246
|
+
<div class="h-2"></div>
|
|
247
|
+
<div class="flex flex-wrap gap-2">
|
|
248
|
+
<button onclick={() => copyToClipboard(cameraPositionCode())} class="text-[0.65rem] font-medium cursor-pointer bg-black/5 rounded-full p-1 pl-4 pr-4 hover:bg-black/10 transition active:bg-blue-200 active:border-blue-200" >
|
|
249
|
+
<div class="flex gap-1 items-center">
|
|
250
|
+
|
|
251
|
+
<p>Copy camera <strong>position</strong></p>
|
|
252
|
+
<img src={moveIcon} alt="Rotation icon" class="w-[15px]"/></div>
|
|
253
|
+
</button>
|
|
254
|
+
<button onclick={() => copyToClipboard(cameraRotationCode())} class="text-[0.65rem] font-medium cursor-pointer bg-black/5 rounded-full p-1 pl-4 pr-4 hover:bg-black/10 transition active:bg-blue-200 active:border-blue-200" >
|
|
255
|
+
<div class="flex gap-1 items-center">
|
|
256
|
+
|
|
257
|
+
<p>Copy camera <strong>rotation</strong></p>
|
|
258
|
+
<img src={rotateIcon} alt="Rotation icon" class="w-[15px]"/></div>
|
|
259
|
+
</button>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<div class="h-6"></div>
|
|
263
|
+
<p class="font-bold text-sm">Details</p>
|
|
264
|
+
<div class="h-2"></div>
|
|
265
|
+
<p class="text-xs">Animation playback FPS: <strong>{(screenRefreshRate/animationFPSDivider).toFixed(2)}</strong> Hz, rendered video FPS: <strong>{(screenRefreshRate/animationFPSDivider/renderSkip).toFixed(2)}</strong> Hz</p>
|
|
266
|
+
<div class="h-2"></div>
|
|
267
|
+
<p class="text-[0.7rem] opacity-50">Hot reload mode: <strong>{hotreloadNameLookup(scene.hotReloadSetting)}</strong></p>
|
|
268
|
+
<p class="text-[0.7rem] opacity-50">Screen refresh rate: <strong>{screenRefreshRate.toFixed(2)}</strong> Hz</p>
|
|
269
|
+
<p class="text-[0.7rem] opacity-50">Animation FPS divider <strong>{animationFPSDivider}</strong></p>
|
|
270
|
+
<p class="text-[0.7rem] opacity-50">Render skip constant <strong>{renderSkip}</strong></p>
|
|
271
|
+
{/if}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
<!--
|
|
103
275
|
<p id="cameraPositionTextID" class="mt-2 text-xs"></p>
|
|
104
276
|
<p id="cameraRotationTextID" class="mt-2 text-xs"></p>
|
|
277
|
+
-->
|
|
105
278
|
</div>
|
|
106
279
|
|
|
107
280
|
<style>
|
|
@@ -121,10 +294,20 @@
|
|
|
121
294
|
/* Thumb style for Chrome */
|
|
122
295
|
input[type='range']::-webkit-slider-thumb {
|
|
123
296
|
-webkit-appearance: none;
|
|
124
|
-
width:
|
|
297
|
+
width: 10px;
|
|
125
298
|
height: 16px;
|
|
126
|
-
border-radius:
|
|
127
|
-
background: #
|
|
299
|
+
border-radius: 5px;
|
|
300
|
+
background: #c2c2c2;
|
|
301
|
+
border: 2px solid #616161;
|
|
128
302
|
margin-top: -6px; /* Center the thumb on the track */
|
|
303
|
+
cursor: pointer;
|
|
304
|
+
transition: all 0.1s ease;
|
|
305
|
+
|
|
306
|
+
/* Larger hitbox using box-shadow trick */
|
|
307
|
+
box-shadow: 0 0 0 8px transparent;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
input[type='range']::-webkit-slider-thumb:hover {
|
|
311
|
+
background: #616161;
|
|
129
312
|
}
|
|
130
313
|
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
|
3
|
+
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
4
|
+
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
|
6
|
+
preserveAspectRatio="xMidYMid meet">
|
|
7
|
+
|
|
8
|
+
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
|
9
|
+
fill="#000000" stroke="none">
|
|
10
|
+
<path d="M2455 5107 c-370 -95 -662 -590 -820 -1392 -21 -104 -39 -192 -41
|
|
11
|
+
-193 -1 -2 -62 -15 -136 -28 -679 -125 -1176 -363 -1363 -654 -70 -109 -89
|
|
12
|
+
-169 -89 -290 0 -90 4 -115 27 -173 89 -229 331 -423 714 -576 302 -120 714
|
|
13
|
+
-212 1141 -257 94 -10 172 -21 172 -25 0 -4 -20 -23 -44 -42 -56 -46 -80 -96
|
|
14
|
+
-73 -153 11 -105 124 -166 214 -116 18 9 99 81 181 160 l150 142 149 0 c144 0
|
|
15
|
+
407 13 527 26 l59 6 -7 -38 c-103 -575 -330 -1064 -545 -1173 -68 -35 -120
|
|
16
|
+
-37 -185 -8 -106 49 -198 155 -303 352 -53 100 -63 112 -106 133 -63 32 -118
|
|
17
|
+
24 -168 -27 -53 -53 -59 -101 -23 -183 130 -292 329 -508 531 -574 86 -28 238
|
|
18
|
+
-26 318 6 261 101 470 384 631 854 51 150 123 432 150 589 16 93 21 108 39
|
|
19
|
+
112 11 2 72 14 135 25 323 59 642 161 878 279 272 137 445 299 519 488 23 58
|
|
20
|
+
27 83 27 173 -1 87 -5 116 -25 170 -99 262 -398 477 -889 640 -100 33 -134 40
|
|
21
|
+
-167 35 -84 -12 -145 -105 -123 -186 17 -60 63 -93 190 -134 282 -93 488 -202
|
|
22
|
+
611 -325 74 -74 103 -129 103 -200 1 -178 -261 -375 -689 -518 -186 -62 -527
|
|
23
|
+
-143 -542 -129 -3 3 0 70 6 149 50 611 2 1310 -125 1838 -31 129 -66 257 -81
|
|
24
|
+
296 -3 9 16 5 59 -12 80 -32 148 -35 186 -7 87 64 96 170 20 240 -12 11 -112
|
|
25
|
+
55 -222 96 -109 42 -199 77 -201 79 -1 2 -21 37 -45 78 -124 217 -280 368
|
|
26
|
+
-445 430 -73 27 -207 35 -280 17z m216 -317 c81 -42 194 -172 257 -296 l21
|
|
27
|
+
-42 -75 -173 c-41 -96 -79 -190 -85 -209 -24 -80 35 -170 120 -185 33 -5 51
|
|
28
|
+
-2 89 17 40 19 52 33 78 87 17 35 34 60 36 55 12 -19 78 -296 102 -424 30
|
|
29
|
+
-157 60 -384 77 -590 24 -272 15 -846 -18 -1127 l-6 -51 -46 -6 c-119 -16
|
|
30
|
+
-295 -27 -501 -32 l-225 -5 -139 161 c-143 167 -172 190 -238 190 -34 0 -93
|
|
31
|
+
-30 -115 -59 -7 -9 -17 -33 -23 -53 -16 -55 2 -107 56 -167 53 -59 59 -56 -97
|
|
32
|
+
-41 -804 81 -1465 332 -1610 611 -31 60 -31 138 0 198 76 147 315 297 658 415
|
|
33
|
+
132 45 360 104 496 129 l79 15 -6 -47 c-14 -107 -26 -360 -26 -552 0 -197 1
|
|
34
|
+
-207 23 -239 36 -54 71 -74 127 -74 54 0 87 18 124 68 20 26 21 46 27 294 7
|
|
35
|
+
268 26 561 39 581 15 26 509 56 787 49 161 -5 187 -3 222 13 53 23 81 70 81
|
|
36
|
+
132 0 85 -42 131 -137 149 -69 13 -606 5 -787 -12 -65 -6 -120 -9 -122 -7 -6
|
|
37
|
+
6 53 282 87 399 131 466 310 766 504 845 40 17 114 9 166 -17z"/>
|
|
38
|
+
</g>
|
|
39
|
+
</svg>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
|
3
|
+
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
4
|
+
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
|
6
|
+
preserveAspectRatio="xMidYMid meet">
|
|
7
|
+
|
|
8
|
+
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
|
9
|
+
fill="#000000" stroke="none">
|
|
10
|
+
<path d="M2485 4781 c-16 -10 -166 -155 -332 -322 -254 -257 -303 -310 -312
|
|
11
|
+
-344 -35 -134 112 -245 234 -177 11 6 88 79 170 161 l150 151 5 -554 c5 -540
|
|
12
|
+
6 -555 26 -582 39 -53 71 -69 134 -69 63 0 95 16 134 69 20 27 21 42 26 582
|
|
13
|
+
l5 554 150 -151 c83 -82 159 -155 170 -161 64 -36 140 -25 191 26 30 30 57 94
|
|
14
|
+
49 119 -2 7 -6 26 -10 42 -5 22 -89 113 -313 338 -169 170 -319 315 -334 323
|
|
15
|
+
-38 20 -107 18 -143 -5z"/>
|
|
16
|
+
<path d="M1035 3285 c-5 -2 -23 -6 -40 -10 -22 -5 -113 -89 -338 -313 -170
|
|
17
|
+
-169 -315 -319 -323 -334 -18 -35 -18 -101 0 -136 8 -15 153 -165 323 -334
|
|
18
|
+
225 -224 316 -308 338 -313 17 -4 35 -8 42 -10 25 -8 89 19 119 49 51 51 62
|
|
19
|
+
127 26 191 -6 11 -79 88 -161 170 l-151 150 554 5 c540 5 555 6 582 26 53 39
|
|
20
|
+
69 71 69 134 0 63 -16 95 -69 134 -27 20 -42 21 -582 26 l-554 5 151 150 c82
|
|
21
|
+
83 155 159 161 170 55 98 0 213 -112 238 -14 3 -29 4 -35 2z"/>
|
|
22
|
+
<path d="M4030 3278 c-96 -27 -142 -143 -92 -233 6 -11 79 -87 161 -170 l151
|
|
23
|
+
-150 -554 -5 c-540 -5 -555 -6 -582 -26 -53 -39 -69 -71 -69 -134 0 -63 16
|
|
24
|
+
-95 69 -134 27 -21 40 -21 584 -24 l556 -2 -161 -163 c-130 -131 -163 -170
|
|
25
|
+
-172 -202 -14 -54 2 -110 43 -151 30 -30 94 -57 119 -49 7 2 26 6 42 10 22 5
|
|
26
|
+
113 89 338 313 170 169 315 319 323 334 8 15 14 45 14 68 0 23 -6 53 -14 68
|
|
27
|
+
-8 15 -153 165 -323 334 -224 223 -316 308 -338 313 -16 4 -37 8 -45 10 -8 2
|
|
28
|
+
-31 -1 -50 -7z"/>
|
|
29
|
+
<path d="M2495 2066 c-37 -17 -70 -52 -84 -89 -7 -19 -11 -211 -11 -570 l0
|
|
30
|
+
-541 -162 161 c-114 112 -172 163 -193 168 -16 4 -35 8 -41 10 -26 8 -94 -19
|
|
31
|
+
-122 -48 -29 -31 -55 -96 -47 -120 2 -7 6 -25 10 -42 5 -22 89 -113 313 -338
|
|
32
|
+
169 -170 319 -315 334 -323 35 -18 101 -18 136 0 15 8 165 153 334 323 224
|
|
33
|
+
225 308 316 313 338 4 17 8 35 10 42 8 25 -19 89 -49 119 -51 51 -127 62 -191
|
|
34
|
+
26 -11 -6 -87 -79 -170 -161 l-150 -151 -5 554 c-5 540 -6 555 -26 582 -11 15
|
|
35
|
+
-32 37 -46 47 -34 25 -113 32 -153 13z"/>
|
|
36
|
+
</g>
|
|
37
|
+
</svg>
|