create-definedmotion 0.1.4 → 0.2.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 +1 -1
- package/template/package-lock.json +313 -59
- package/template/package.json +1 -0
- package/template/src/entry.ts +3 -4
- package/template/src/example_scenes/alternativesScene.ts +1 -1
- package/template/src/example_scenes/fourierSeriesScene.ts +3 -3
- package/template/src/example_scenes/keyboardScene.ts +11 -11
- package/template/src/example_scenes/tests/animations/test_updater.ts +24 -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/tutorials/easy1.ts +4 -4
- package/template/src/example_scenes/tutorials/easy3.ts +1 -1
- package/template/src/example_scenes/visulizingFunctions.ts +2 -2
- package/template/src/main/index.ts +59 -3
- package/template/src/preload/index.ts +15 -1
- package/template/src/renderer/index.html +1 -1
- package/template/src/renderer/src/App.svelte +178 -23
- 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/captureCanvas.ts +2 -2
- package/template/src/renderer/src/lib/animation/interpolations.ts +2 -1
- package/template/src/renderer/src/lib/general/helpers.ts +16 -47
- package/template/src/renderer/src/lib/scene/sceneClass.ts +100 -33
- package/template/src/renderer/src/lib/animation/helpers.ts +0 -7
|
@@ -5,17 +5,29 @@ import {
|
|
|
5
5
|
type InternalAnimation,
|
|
6
6
|
type UserAnimation
|
|
7
7
|
} from '../animation/protocols'
|
|
8
|
-
import { generateID
|
|
8
|
+
import { generateID } from '../general/helpers'
|
|
9
9
|
import { sleep } from '../rendering/helpers'
|
|
10
10
|
import { createScene } from '../rendering/setup'
|
|
11
11
|
import * as THREE from 'three'
|
|
12
12
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
|
13
13
|
import { easeConstant } from '../animation/interpolations'
|
|
14
|
-
import {
|
|
14
|
+
import { animationFPSDivider, renderSkip } from '../../../../entry'
|
|
15
15
|
import { addDestroyFunction } from '../general/onDestory'
|
|
16
|
-
import { ticksToMillis } from '../animation/helpers'
|
|
17
16
|
import { AudioInScene, loadAllAudio, playAudio, registerAudio } from '../audio/loader'
|
|
18
17
|
|
|
18
|
+
export const screenFPS = await (window.api as any).getDisplayHz(); //Your screen fps
|
|
19
|
+
|
|
20
|
+
const timelineFPS = screenFPS / animationFPSDivider;
|
|
21
|
+
|
|
22
|
+
// Convert ticks (frames) to milliseconds
|
|
23
|
+
export const ticksToMillis = (ticks: number) => (ticks / timelineFPS) * 1000
|
|
24
|
+
|
|
25
|
+
// Convert milliseconds to the closest whole number of ticks
|
|
26
|
+
export const millisToTicks = (ms: number) => Math.ceil((ms / 1000) * timelineFPS)
|
|
27
|
+
|
|
28
|
+
export const renderOutputFps = () => timelineFPS / renderSkip
|
|
29
|
+
|
|
30
|
+
|
|
19
31
|
export enum SpaceSetting {
|
|
20
32
|
ThreeDim,
|
|
21
33
|
TwoDim
|
|
@@ -23,7 +35,19 @@ export enum SpaceSetting {
|
|
|
23
35
|
|
|
24
36
|
export enum HotReloadSetting {
|
|
25
37
|
TraceFromStart,
|
|
26
|
-
BeginFromCurrent
|
|
38
|
+
BeginFromCurrent,
|
|
39
|
+
BeginFreshOnSave
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const hotreloadNameLookup = (mode: HotReloadSetting) => {
|
|
43
|
+
switch (mode) {
|
|
44
|
+
case HotReloadSetting.TraceFromStart:
|
|
45
|
+
return "Trace from start";
|
|
46
|
+
case HotReloadSetting.BeginFromCurrent:
|
|
47
|
+
return "Begin from current frame without trace";
|
|
48
|
+
case HotReloadSetting.BeginFreshOnSave:
|
|
49
|
+
return "Go to the beginning";
|
|
50
|
+
}
|
|
27
51
|
}
|
|
28
52
|
|
|
29
53
|
type SceneInstruction = (tick: number) => any
|
|
@@ -54,6 +78,8 @@ export class AnimatedScene {
|
|
|
54
78
|
|
|
55
79
|
playEffectFunction: () => any = () => {}
|
|
56
80
|
|
|
81
|
+
renderingEventFunction: (start: boolean) => any = () => {}
|
|
82
|
+
|
|
57
83
|
isPlaying = false
|
|
58
84
|
|
|
59
85
|
private initialSceneChildren: THREE.Object3D[] = []
|
|
@@ -77,6 +103,7 @@ export class AnimatedScene {
|
|
|
77
103
|
|
|
78
104
|
private buildFunction: (scene: this) => any
|
|
79
105
|
|
|
106
|
+
public hotReloadSetting: HotReloadSetting
|
|
80
107
|
private traceFromStart: boolean
|
|
81
108
|
|
|
82
109
|
private controlsAnimationFrameId: number | null = null
|
|
@@ -87,6 +114,9 @@ export class AnimatedScene {
|
|
|
87
114
|
private doNotPlayAudio = false
|
|
88
115
|
private renderingAudioGather: AudioInScene[] = []
|
|
89
116
|
|
|
117
|
+
private playbackTargetDistance: number | null = null
|
|
118
|
+
|
|
119
|
+
|
|
90
120
|
constructor(
|
|
91
121
|
pixelsWidth: number,
|
|
92
122
|
pixelsHeight: number,
|
|
@@ -97,7 +127,8 @@ export class AnimatedScene {
|
|
|
97
127
|
this.container = globalContainerRef
|
|
98
128
|
this.pixelsHeight = pixelsHeight
|
|
99
129
|
this.pixelsWidth = pixelsWidth
|
|
100
|
-
this.
|
|
130
|
+
this.hotReloadSetting = hotReloadSetting
|
|
131
|
+
this.traceFromStart = hotReloadSetting !== HotReloadSetting.BeginFromCurrent
|
|
101
132
|
|
|
102
133
|
const threeDim = spaceSetting === SpaceSetting.ThreeDim
|
|
103
134
|
|
|
@@ -130,6 +161,9 @@ export class AnimatedScene {
|
|
|
130
161
|
this.renderer = renderer
|
|
131
162
|
this.controls = controls
|
|
132
163
|
|
|
164
|
+
// Cap viewer pixel ratio, without this HDRIs become super slow on MacBooks for example
|
|
165
|
+
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1));
|
|
166
|
+
|
|
133
167
|
this.startControls()
|
|
134
168
|
|
|
135
169
|
addDestroyFunction(() => this.onDestroy())
|
|
@@ -148,7 +182,16 @@ export class AnimatedScene {
|
|
|
148
182
|
this.appendInstruction(instruction, this.sceneCalculationTick)
|
|
149
183
|
}
|
|
150
184
|
|
|
151
|
-
|
|
185
|
+
doAt(tick: number, instruction: SceneInstruction) {
|
|
186
|
+
if (tick < 0) throw new Error('doAt: tick must be ≥ 0')
|
|
187
|
+
this.appendInstruction(instruction, tick)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
getCurrentTimeMs() {
|
|
191
|
+
return ticksToMillis(this.sceneRenderTick)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
addAnims(...animations: UserAnimation[]) {
|
|
152
195
|
const longest = Math.max(...animations.map((a) => a.interpolation.length))
|
|
153
196
|
for (const animation of animations) {
|
|
154
197
|
this.appendAnimation(animation)
|
|
@@ -156,7 +199,7 @@ export class AnimatedScene {
|
|
|
156
199
|
this.sceneCalculationTick += longest
|
|
157
200
|
}
|
|
158
201
|
|
|
159
|
-
|
|
202
|
+
insertAnimsAt(tick: number, ...animations: UserAnimation[]) {
|
|
160
203
|
for (const animation of animations) {
|
|
161
204
|
const internalAnimation: InternalAnimation = {
|
|
162
205
|
startTick: tick,
|
|
@@ -169,6 +212,21 @@ export class AnimatedScene {
|
|
|
169
212
|
}
|
|
170
213
|
}
|
|
171
214
|
|
|
215
|
+
addDeferredAnims(...futureAnimations: (() => UserAnimation)[]) {
|
|
216
|
+
// Execute once during planning just to get durations
|
|
217
|
+
const tempAnims = futureAnimations.map(fn => fn())
|
|
218
|
+
const longest = Math.max(...tempAnims.map((a) => a.interpolation.length))
|
|
219
|
+
|
|
220
|
+
this.do((tick) => {
|
|
221
|
+
const calculatedAnimations: UserAnimation[] = []
|
|
222
|
+
for (const futureAnimation of futureAnimations) {
|
|
223
|
+
calculatedAnimations.push(futureAnimation()) // Execute again at runtime
|
|
224
|
+
}
|
|
225
|
+
this.insertAnimsAt(tick, ...calculatedAnimations)
|
|
226
|
+
})
|
|
227
|
+
this.sceneCalculationTick += longest
|
|
228
|
+
}
|
|
229
|
+
|
|
172
230
|
addSequentialBackgroundAnims(...sequentialAnimations: UserAnimation[]) {
|
|
173
231
|
let padding = 0
|
|
174
232
|
for (const animation of sequentialAnimations) {
|
|
@@ -182,7 +240,7 @@ export class AnimatedScene {
|
|
|
182
240
|
}
|
|
183
241
|
|
|
184
242
|
end() {
|
|
185
|
-
this.totalSceneTicks = this.sceneCalculationTick
|
|
243
|
+
this.totalSceneTicks = this.sceneCalculationTick
|
|
186
244
|
}
|
|
187
245
|
|
|
188
246
|
registerAudio(audioPath: string) {
|
|
@@ -221,7 +279,7 @@ export class AnimatedScene {
|
|
|
221
279
|
}
|
|
222
280
|
|
|
223
281
|
addWait(duration: number) {
|
|
224
|
-
this.
|
|
282
|
+
this.addAnims(createAnim(easeConstant(0, duration), () => {}))
|
|
225
283
|
}
|
|
226
284
|
|
|
227
285
|
async jumpToFrameAtIndex(index: number, notSize: boolean = false) {
|
|
@@ -261,24 +319,17 @@ export class AnimatedScene {
|
|
|
261
319
|
}
|
|
262
320
|
|
|
263
321
|
private syncControlsWithCamera() {
|
|
264
|
-
|
|
265
|
-
|
|
322
|
+
const dir = new THREE.Vector3();
|
|
323
|
+
this.camera.getWorldDirection(dir); // works for both camera types
|
|
266
324
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
} else {
|
|
271
|
-
direction.applyQuaternion(this.camera.quaternion)
|
|
272
|
-
}
|
|
325
|
+
const distance =
|
|
326
|
+
this.playbackTargetDistance ??
|
|
327
|
+
this.controls.target.distanceTo(this.camera.position);
|
|
273
328
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// Reset the internal state
|
|
280
|
-
this.controls.update()
|
|
281
|
-
}
|
|
329
|
+
const newTarget = this.camera.position.clone().add(dir.multiplyScalar(distance));
|
|
330
|
+
this.controls.target.copy(newTarget);
|
|
331
|
+
this.controls.update();
|
|
332
|
+
}
|
|
282
333
|
|
|
283
334
|
private startControls() {
|
|
284
335
|
this.controls.enabled = true
|
|
@@ -317,10 +368,6 @@ export class AnimatedScene {
|
|
|
317
368
|
|
|
318
369
|
this.renderCurrentFrame()
|
|
319
370
|
animateCounter++
|
|
320
|
-
|
|
321
|
-
if (animateCounter % 10 === 0) {
|
|
322
|
-
logCameraState(this.camera)
|
|
323
|
-
}
|
|
324
371
|
}
|
|
325
372
|
animate()
|
|
326
373
|
}
|
|
@@ -357,12 +404,16 @@ export class AnimatedScene {
|
|
|
357
404
|
this.isPlaying = false
|
|
358
405
|
if (this.animationFrameId) cancelAnimationFrame(this.animationFrameId)
|
|
359
406
|
|
|
360
|
-
this.syncControlsWithCamera()
|
|
361
407
|
|
|
362
|
-
|
|
408
|
+
// use the captured distance one last time
|
|
409
|
+
this.syncControlsWithCamera();
|
|
410
|
+
this.playbackTargetDistance = null;
|
|
411
|
+
|
|
412
|
+
this.startControls();
|
|
363
413
|
}
|
|
364
414
|
|
|
365
415
|
async render() {
|
|
416
|
+
this.renderingEventFunction(true)
|
|
366
417
|
this.isRendering = true
|
|
367
418
|
this.isPlaying = true
|
|
368
419
|
this.stopControls()
|
|
@@ -425,6 +476,7 @@ export class AnimatedScene {
|
|
|
425
476
|
|
|
426
477
|
this.isPlaying = false
|
|
427
478
|
this.startControls()
|
|
479
|
+
this.renderingEventFunction(false)
|
|
428
480
|
}
|
|
429
481
|
|
|
430
482
|
play() {
|
|
@@ -435,17 +487,32 @@ export class AnimatedScene {
|
|
|
435
487
|
this.isPlaying = true
|
|
436
488
|
this.stopControls()
|
|
437
489
|
await this.jumpToFrameAtIndex(fromFrame)
|
|
438
|
-
|
|
490
|
+
|
|
491
|
+
// Capture a distance that OrbitControls will keep during play
|
|
492
|
+
this.playbackTargetDistance =
|
|
493
|
+
this.controls.target.distanceTo(this.camera.position)
|
|
439
494
|
|
|
440
495
|
let currentFrame = fromFrame
|
|
441
496
|
let numberCalledAnimate = 0
|
|
442
497
|
const animate = async (trace: boolean) => {
|
|
443
498
|
if (!this.isPlaying) return
|
|
444
499
|
if (currentFrame <= toFrame) {
|
|
445
|
-
|
|
500
|
+
// Still modulus since the requestAnimationFrame runs at the screenFPS rate, not timelineFPS rate
|
|
501
|
+
if (numberCalledAnimate % animationFPSDivider === 0) {
|
|
446
502
|
this.sceneRenderTick = currentFrame
|
|
447
503
|
//To not apply trace twice if we just jumped to startframe (and thus tranced it)
|
|
448
504
|
await this.traceCurrentFrame(this.sceneRenderTick, true, !trace)
|
|
505
|
+
|
|
506
|
+
// --- Keep controls.target aligned with the animated camera ---
|
|
507
|
+
if (this.playbackTargetDistance != null) {
|
|
508
|
+
const camDir = new THREE.Vector3()
|
|
509
|
+
this.camera.getWorldDirection(camDir) // forward (-Z in view space)
|
|
510
|
+
const target = this.camera.position.clone()
|
|
511
|
+
.add(camDir.multiplyScalar(this.playbackTargetDistance))
|
|
512
|
+
this.controls.target.copy(target)
|
|
513
|
+
this.controls.update() // ok to call while disabled; just updates internals
|
|
514
|
+
}
|
|
515
|
+
|
|
449
516
|
this.renderCurrentFrame()
|
|
450
517
|
currentFrame++
|
|
451
518
|
await this.playEffectFunction()
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { screenFps } from '../../../../entry'
|
|
2
|
-
|
|
3
|
-
// Convert ticks (frames) to milliseconds
|
|
4
|
-
export const ticksToMillis = (ticks: number) => (ticks / screenFps) * 1000
|
|
5
|
-
|
|
6
|
-
// Convert milliseconds to the closest whole number of ticks
|
|
7
|
-
export const millisToTicks = (ms: number) => Math.ceil((ms / 1000) * screenFps)
|