diff --git a/media/src/form-components/PlayButtons.svelte b/media/src/form-components/PlayButtons.svelte index 13845593..aab32d82 100644 --- a/media/src/form-components/PlayButtons.svelte +++ b/media/src/form-components/PlayButtons.svelte @@ -5,6 +5,8 @@ play = () => {}, pause, rew, + clicker = () => {}, + playMode = 'loop', // animate, } = $props(); @@ -32,6 +34,15 @@ + diff --git a/media/src/objects/Curve.svelte b/media/src/objects/Curve.svelte index 914c341f..7c79718a 100644 --- a/media/src/objects/Curve.svelte +++ b/media/src/objects/Curve.svelte @@ -44,6 +44,7 @@ camera, gridStep, selected = $bindable(false), + playMode = 'loop', } = $props(); title = title || `Curve ${++titleIndex}`; @@ -163,15 +164,42 @@ let last; + function playModeCycle() { + if (playMode == 'once') { + playMode = 'loop'; + } else if (playMode == 'loop') { + playMode = 'bounce'; + } else { + playMode = 'once'; + } + } + + let modeSign = 1; + const update = (dt = 0) => { - const { a, b } = params; + // const { a, b } = params; if (vizOptions.TNB) { - tau += dt / arrows.v.speed / (B - A); + tau += (modeSign * dt) / arrows.v.speed / (B - A); } else { - tau += dt / (B - A); + tau += (modeSign * dt) / (B - A); } - tau %= 1; + if (tau > 1) { + if (playMode == 'loop') { + tau %= 1; + } else if (playMode == 'once') { + tau = 1; + animation = false; + } else { + modeSign = -1; + tau = 2 - tau; + } + } + if (tau <= 0) { + modeSign = 1; + tau *= -1; + } + // const T = A + (B - A) * tau; // uncomment this if we want aVal to be animated: @@ -663,6 +691,8 @@ animation = false; update(); }} + {playMode} + clicker={playModeCycle} /> {#if isDynamic} diff --git a/media/src/objects/Function.svelte b/media/src/objects/Function.svelte index f201443c..5411f6e9 100644 --- a/media/src/objects/Function.svelte +++ b/media/src/objects/Function.svelte @@ -67,6 +67,7 @@ animate, camera, onClose = () => {}, + playMode = 'loop', } = $props(); // $inspect(camera); @@ -577,6 +578,16 @@ */ let isDynamic = $derived(dependsOn(params, 't')); + function playModeCycle() { + if (playMode == 'once') { + playMode = 'loop'; + } else if (playMode == 'loop') { + playMode = 'bounce'; + } else { + playMode = 'once'; + } + } + $effect(() => { [params.z, params.a, params.b, params.c, params.d]; untrack(updateSurface); @@ -607,9 +618,25 @@ if (selected) untrack(flash); }); + let modeSign = 1; + const update = function (dt) { - tau += dt / (t1 - t0); - tau %= 1; + tau += (modeSign * dt) / (t1 - t0); + if (tau > 1) { + if (playMode == 'loop') { + tau %= 1; + } else if (playMode == 'once') { + tau = 1; + animation = false; + } else { + modeSign = -1; + tau = 2 - tau; + } + } + if (tau <= 0) { + modeSign = 1; + tau *= -1; + } evolveSurface(tVal); @@ -799,26 +826,25 @@ boxMesh.add(boxMeshEdges); const updateBoxes = function () { - const { a, b, c, d} = params; + const { a, b, c, d } = params; try { - [ + [ math.evaluate(a), math.evaluate(b), math.evaluate(c), math.evaluate(d), ]; } catch (e) { - console.error("Can't show integral boxes on nonconstant bounds",e); + console.error("Can't show integral boxes on nonconstant bounds", e); return; } const [A, B, C, D] = [ - math.evaluate(a), - math.evaluate(b), - math.evaluate(c), - math.evaluate(d), - ]; - + math.evaluate(a), + math.evaluate(b), + math.evaluate(c), + math.evaluate(d), + ]; // const t = T0 + tau * (T1 - T0); @@ -1181,6 +1207,8 @@ evolveSurface(tVal); render(); }} + {playMode} + clicker={playModeCycle} /> {/if} diff --git a/media/src/objects/Point.svelte b/media/src/objects/Point.svelte index d68a187e..5d81d0db 100644 --- a/media/src/objects/Point.svelte +++ b/media/src/objects/Point.svelte @@ -45,6 +45,7 @@ selectObject, animate, onClose = () => {}, + playMode = 'loop', } = $props(); let tau = $state(0); @@ -106,6 +107,16 @@ let isDynamic = $derived(dependsOn(params, 't')); let isDiscrete = $derived(dependsOn(params, 'n')); + function playModeCycle() { + if (playMode == 'once') { + playMode = 'loop'; + } else if (playMode == 'loop') { + playMode = 'bounce'; + } else { + playMode = 'once'; + } + } + $effect(() => { params; updatePoint(); @@ -213,9 +224,25 @@ // texString1 = `t = ${Math.round(100 * T) / 100}`; + let modeSign = 1; + const update = (dt = 0) => { - tau += dt / (t1 - t0); - tau %= 1; + tau += (modeSign * dt) / (t1 - t0); + if (tau > 1) { + if (playMode == 'loop') { + tau %= 1; + } else if (playMode == 'once') { + tau = 1; + animation = false; + } else { + modeSign = -1; + tau = 2 - tau; + } + } + if (tau <= 0) { + modeSign = 1; + tau *= -1; + } updatePoint(tVal); }; // Start animating if animation changes (e.g. animating scene published) @@ -308,6 +335,8 @@ tau = 0; update(); }} + {playMode} + clicker={playModeCycle} /> {/if} diff --git a/media/src/objects/Surface.svelte b/media/src/objects/Surface.svelte index 56f52132..abd3f928 100644 --- a/media/src/objects/Surface.svelte +++ b/media/src/objects/Surface.svelte @@ -68,6 +68,7 @@ selectObject, selectPoint, animate = () => {}, + playMode = 'loop', } = $props(); let rNum = 10; @@ -588,9 +589,36 @@ } }; + let modeSign = 1; + + function playModeCycle() { + if (playMode == 'once') { + playMode = 'loop'; + } else if (playMode == 'loop') { + playMode = 'bounce'; + } else { + playMode = 'once'; + } + } + const update = function (dt = 0) { - tau += dt / (t1 - t0); - tau %= 1; + tau += (modeSign * dt) / (t1 - t0); + + if (tau > 1) { + if (playMode == 'loop') { + tau %= 1; + } else if (playMode == 'once') { + tau = 1; + animation = false; + } else { + modeSign = -1; + tau = 2 - tau; + } + } + if (tau <= 0) { + modeSign = 1; + tau *= -1; + } if (isDynamic) evolveSurface(tVal); if (isRhoDynamic) { @@ -1062,6 +1090,8 @@ tau = 0; update(); }} + {playMode} + clicker={playModeCycle} /> {/if} diff --git a/media/src/objects/Vector.svelte b/media/src/objects/Vector.svelte index 5b38c29b..15787d87 100644 --- a/media/src/objects/Vector.svelte +++ b/media/src/objects/Vector.svelte @@ -59,6 +59,7 @@ render, onClose, gridStep, + playMode = 'loop', } = $props(); let minimize = $state(false); @@ -143,6 +144,16 @@ let isDynamic = $derived(dependsOn(params, 't')); let isDiscrete = $derived(dependsOn(params, 'n')); + function playModeCycle() { + if (playMode == 'once') { + playMode = 'loop'; + } else if (playMode == 'loop') { + playMode = 'bounce'; + } else { + playMode = 'once'; + } + } + $effect(updateVector); // recolor on demand @@ -250,13 +261,25 @@ return valuation; }; - const update = (dt = 0) => { - const { t0, t1 } = params; - const A = math.parse(t0).evaluate(); - const B = math.parse(t1).evaluate(); + let modeSign = 1; - tau += dt / (B - A); - tau %= 1; + const update = (dt = 0) => { + tau += (modeSign * dt) / (t1 - t0); + if (tau > 1) { + if (playMode == 'loop') { + tau %= 1; + } else if (playMode == 'once') { + tau = 1; + animation = false; + } else { + modeSign = -1; + tau = 2 - tau; + } + } + if (tau <= 0) { + modeSign = 1; + tau *= -1; + } updateVector(tVal); }; @@ -363,6 +386,8 @@ tau = 0; update(); }} + {playMode} + clicker={playModeCycle} /> {/if} diff --git a/media/src/stories/FluxIntegral.svelte b/media/src/stories/FluxIntegral.svelte index e93b29fa..e496573a 100644 --- a/media/src/stories/FluxIntegral.svelte +++ b/media/src/stories/FluxIntegral.svelte @@ -46,7 +46,7 @@ // const currentTime = $tickTock; last = last || $tickTock; tau += $tickTock - last; - tau %= 1; + if (tau > 1) tau %= 1; updateTau(tau); last = $tickTock; }