Browse Source

exportable trajectory animation

dsehnal 4 years ago
parent
commit
e955dc7e94
1 changed files with 69 additions and 20 deletions
  1. 69 20
      src/mol-plugin-state/animation/built-in/model-index.ts

+ 69 - 20
src/mol-plugin-state/animation/built-in/model-index.ts

@@ -14,13 +14,24 @@ import { PluginStateAnimation } from '../model';
 export const AnimateModelIndex = PluginStateAnimation.create({
     name: 'built-in.animate-model-index',
     display: { name: 'Animate Trajectory' },
+    isExportable: true,
     params: () => ({
-        mode: PD.MappedStatic('palindrome', {
+        mode: PD.MappedStatic('loop', {
             palindrome: PD.Group({ }),
             loop: PD.Group({ }),
             once: PD.Group({ direction: PD.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]) }, { isFlat: true })
         }, { options: [['palindrome', 'Palindrome'], ['loop', 'Loop'], ['once', 'Once']] }),
-        maxFPS: PD.Numeric(15, { min: 1, max: 60, step: 1 })
+        duration: PD.MappedStatic('fixed', {
+            fixed: PD.Group({
+                durationInS: PD.Numeric(5, { min: 1, max: 120, step: 0.1 }, { description: 'Duration in seconds' })
+            }, { isFlat: true }),
+            computed: PD.Group({
+                targetFps: PD.Numeric(30, { min: 5, max: 250, step: 1 }, { label: 'Target FPS' })
+            }, { isFlat: true }),
+            sequential: PD.Group({
+                maxFps: PD.Numeric(30, { min: 5, max: 60, step: 1 })
+            }, { isFlat: true })
+        })
     }),
     canApply(ctx) {
         const state = ctx.state.data;
@@ -31,10 +42,30 @@ export const AnimateModelIndex = PluginStateAnimation.create({
         }
         return { canApply: false, reason: 'No trajectory to animate' };
     },
+    getDuration: (p, ctx) => {
+        if (p.duration?.name === 'fixed') {
+            return { kind: 'fixed', durationMs: p.duration.params.durationInS * 1000 };
+        } else if (p.duration.name === 'computed') {
+            const state = ctx.state.data;
+            const models = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Model.ModelFromTrajectory));
+
+            let maxDuration = 0;
+            for (const m of models) {
+                const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, PluginStateObject.Molecule.Trajectory);
+                if (!parent || !parent.obj) continue;
+                const traj = parent.obj;
+                maxDuration = Math.max(Math.ceil(1000 * traj.data.frameCount / p.duration.params.targetFps), maxDuration);
+            }
+
+            return { kind: 'fixed', durationMs: maxDuration };
+        }
+        return { kind: 'unknown' };
+    },
     initialState: () => ({} as { palindromeDirections?: { [id: string]: -1 | 1 | undefined } }),
     async apply(animState, t, ctx) {
         // limit fps
-        if (t.current > 0 && t.current - t.lastApplied < 1000 / ctx.params.maxFPS) {
+
+        if (ctx.params.duration.name === 'sequential' && t.current > 0 && t.current - t.lastApplied < 1000 / ctx.params.duration.params.maxFps) {
             return { kind: 'skip' };
         }
 
@@ -65,27 +96,45 @@ export const AnimateModelIndex = PluginStateAnimation.create({
                 } else {
                     return old;
                 }
-                let dir: -1 | 1 = 1;
-                if (params.mode.name === 'once') {
-                    dir = params.mode.params.direction === 'backward' ? -1 : 1;
-                    // if we are at start or end already, do nothing.
-                    if ((dir === -1 && old.modelIndex === 0) || (dir === 1 && old.modelIndex === len - 1)) {
-                        isEnd = true;
-                        return old;
+
+                if (params.duration.name === 'sequential') {
+                    let dir: -1 | 1 = 1;
+                    if (params.mode.name === 'once') {
+                        dir = params.mode.params.direction === 'backward' ? -1 : 1;
+                        // if we are at start or end already, do nothing.
+                        if ((dir === -1 && old.modelIndex === 0) || (dir === 1 && old.modelIndex === len - 1)) {
+                            isEnd = true;
+                            return old;
+                        }
+                    } else if (params.mode.name === 'palindrome') {
+                        if (old.modelIndex === 0) dir = 1;
+                        else if (old.modelIndex === len - 1) dir = -1;
+                        else dir = palindromeDirections[m.transform.ref] || 1;
                     }
-                } else if (params.mode.name === 'palindrome') {
-                    if (old.modelIndex === 0) dir = 1;
-                    else if (old.modelIndex === len - 1) dir = -1;
-                    else dir = palindromeDirections[m.transform.ref] || 1;
-                }
-                palindromeDirections[m.transform.ref] = dir;
+                    palindromeDirections[m.transform.ref] = dir;
 
-                let modelIndex = (old.modelIndex + dir) % len;
-                if (modelIndex < 0) modelIndex += len;
+                    let modelIndex = (old.modelIndex + dir) % len;
+                    if (modelIndex < 0) modelIndex += len;
 
-                isEnd = isEnd || (dir === -1 && modelIndex === 0) || (dir === 1 && modelIndex === len - 1);
+                    isEnd = isEnd || (dir === -1 && modelIndex === 0) || (dir === 1 && modelIndex === len - 1);
 
-                return { modelIndex };
+                    return { modelIndex };
+                } else {
+                    const durationInMs =  params.duration.name === 'fixed'
+                        ? params.duration.params.durationInS * 1000
+                        : Math.ceil(1000 * traj.data.frameCount / params.duration.params.targetFps);
+
+                    let phase: number = (t.current % durationInMs) / durationInMs;
+
+                    if (params.mode.name === 'palindrome') {
+                        phase = 2 * phase;
+                        if (phase > 1) phase =  2 - phase;
+                    }
+
+                    const modelIndex = Math.min(Math.floor(traj.data.frameCount * phase), traj.data.frameCount - 1);
+                    isEnd = isEnd || modelIndex === traj.data.frameCount - 1;
+                    return { modelIndex };
+                }
             });
         }