Browse Source

animation improvements/fixes
- added AnimateStateSnapshots
- added "getAnimationDuration" to exportable animations

David Sehnal 4 years ago
parent
commit
a71186905d

+ 7 - 1
src/mol-plugin-state/animation/built-in/assembly-unwind.ts

@@ -36,8 +36,14 @@ export const AnimateAssemblyUnwind = PluginStateAnimation.create({
         const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D, root));
         return { canApply: reprs.length > 0 };
     },
+    getDuration: (params) => {
+        return {
+            kind: 'fixed',
+            durationMs: params.durationInMs
+        };
+    },
     initialState: () => ({ t: 0 }),
-    setup(params, plugin) {
+    setup(params, _, plugin) {
         const state = plugin.state.data;
         const root = !params.target || params.target === 'all' ? StateTransform.RootRef : params.target;
         const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D, root));

+ 1 - 1
src/mol-plugin-state/animation/built-in/explode-units.ts

@@ -18,7 +18,7 @@ export const AnimateUnitsExplode = PluginStateAnimation.create({
         durationInMs: PD.Numeric(3000, { min: 100, max: 10000, step: 100})
     }),
     initialState: () => ({ t: 0 }),
-    async setup(_, plugin) {
+    async setup(_, __, plugin) {
         const state = plugin.state.data;
         const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D));
 

+ 84 - 0
src/mol-plugin-state/animation/built-in/state-snapshots.ts

@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { PluginContext } from '../../../mol-plugin/context';
+import { PluginStateSnapshotManager } from '../../manager/snapshots';
+import { PluginStateAnimation } from '../model';
+
+async function setPartialSnapshot(plugin: PluginContext, entry: PluginStateSnapshotManager.Entry, first = false) {
+    if (entry.snapshot.data) {
+        await plugin.runTask(plugin.state.data.setSnapshot(entry.snapshot.data));
+    }
+    if (entry.snapshot.camera) {
+        plugin.canvas3d?.requestCameraReset({
+            snapshot: entry.snapshot.camera.current,
+            durationMs: first || entry.snapshot.camera.transitionStyle === 'instant'
+                ? 0 : entry.snapshot.camera.transitionDurationInMs
+        });
+    }
+}
+
+type State = { totalDuration: number, snapshots: PluginStateSnapshotManager.Entry[], currentIndex: 0 };
+
+export const AnimateStateSnapshots = PluginStateAnimation.create({
+    name: 'built-in.animate-state-snapshots',
+    display: { name: 'State Snapshots' },
+    isExportable: true,
+    params: () => ({ }),
+    canApply(plugin) {
+        const entries = plugin.managers.snapshot.state.entries;
+        if (entries.size < 2) {
+            return { canApply: false, reason: 'At least 2 states required.' };
+        }
+        if (entries.some(e => !!e?.snapshot.startAnimation)) {
+            return { canApply: false, reason: 'Nested animations not supported.' };
+        }
+        return { canApply: plugin.managers.snapshot.state.entries.size > 1 };
+    },
+    setup(_, __, plugin) {
+        const pivot = plugin.managers.snapshot.state.entries.get(0)!;
+        setPartialSnapshot(plugin, pivot, true);
+    },
+    getDuration: (_, plugin) => {
+        return {
+            kind: 'fixed',
+            durationMs: plugin.managers.snapshot.state.entries.toArray().reduce((a, b) => a + (b.snapshot.durationInMs ?? 0), 0)
+        };
+    },
+    initialState: (_, plugin) => {
+        const snapshots = plugin.managers.snapshot.state.entries.toArray();
+
+        return {
+            totalDuration: snapshots.reduce((a, b) => a + (b.snapshot.durationInMs ?? 0), 0),
+            snapshots,
+            currentIndex: 0
+        } as State;
+    },
+    async apply(animState: State, t, ctx) {
+        if (t.current >= animState.totalDuration) {
+            return { kind: 'finished' };
+        }
+
+        let ctime = 0, i = 0;
+        for (const s of animState.snapshots) {
+            ctime += s.snapshot.durationInMs ?? 0;
+            if (t.current < ctime) {
+                break;
+            }
+            i++;
+        }
+
+        if (i >= animState.snapshots.length) return { kind: 'finished' };
+
+        if (i === animState.currentIndex) {
+            return { kind: 'skip' };
+        }
+
+        setPartialSnapshot(ctx.plugin, animState.snapshots[i]);
+
+        return { kind: 'next', state: { ...animState, currentIndex: i } };
+    }
+});

+ 2 - 2
src/mol-plugin-state/animation/model.ts

@@ -22,8 +22,8 @@ interface PluginStateAnimation<P = any, S = any> {
     initialState(params: P, ctx: PluginContext): S,
     getDuration?(params: P, ctx: PluginContext): PluginStateAnimation.Duration,
 
-    // TODO: support state in setup/teardown?
-    setup?(params: P, ctx: PluginContext): void | Promise<void>,
+    // Optionally returns new state
+    setup?(params: P, state: S, ctx: PluginContext): void | Promise<S> | Promise<void>,
     teardown?(params: P, state: S, ctx: PluginContext): void | Promise<void>,
 
     /**

+ 5 - 3
src/mol-plugin-state/manager/animation.ts

@@ -121,13 +121,15 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
         this.triggerUpdate();
 
         const anim = this._current.anim;
+        let initialState = this._current.anim.initialState(this._current.paramValues, this.context);
         if (anim.setup) {
-            await anim.setup(this._current.paramValues, this.context);
+            const state = await anim.setup(this._current.paramValues, initialState, this.context);
+            if (state) initialState = state;
         }
 
         this._current.lastTime = 0;
         this._current.startedTime = -1;
-        this._current.state = this._current.anim.initialState(this._current.paramValues, this.context);
+        this._current.state = initialState;
         this.isStopped = false;
     }
 
@@ -215,7 +217,7 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
             this.context.behaviors.state.isAnimating.next(true);
         }
         if (anim.setup) {
-            await anim.setup(this._current.paramValues, this.context);
+            await anim.setup(this._current.paramValues, this._current.state, this.context);
         }
         this.isStopped = false;
     }

+ 2 - 0
src/mol-plugin/index.ts

@@ -20,6 +20,7 @@ import { AssignColorVolume } from '../mol-plugin-state/actions/volume';
 import { AnimateModelIndex } from '../mol-plugin-state/animation/built-in/model-index';
 import { AnimateAssemblyUnwind } from '../mol-plugin-state/animation/built-in/assembly-unwind';
 import { AnimateCameraSpin } from '../mol-plugin-state/animation/built-in/camera-spin';
+import { AnimateStateSnapshots } from '../mol-plugin-state/animation/built-in/state-snapshots';
 
 export const DefaultPluginSpec: PluginSpec = {
     actions: [
@@ -89,6 +90,7 @@ export const DefaultPluginSpec: PluginSpec = {
     animations: [
         AnimateModelIndex,
         AnimateCameraSpin,
+        AnimateStateSnapshots,
         AnimateAssemblyUnwind
     ]
 };