Browse Source

Merge pull request #341 from molstar/fix-camera-spin-stutter

Pass animation info to state animations
David Sehnal 3 years ago
parent
commit
3134e1d9f9

+ 2 - 0
CHANGELOG.md

@@ -7,6 +7,8 @@ Note that since we don't clearly distinguish between a public and private interf
 ## [Unreleased]
 
 - Fix ``getOperatorsForIndex``
+- Pass animation info (current frame & count) to state animations
+    - Fix camera stutter for "camera spin" animation
 
 ## [v3.0.0-dev.9] - 2022-01-09
 

+ 1 - 1
src/extensions/mp4-export/encoder.ts

@@ -73,7 +73,7 @@ export async function encodeMp4Animation<A extends PluginStateAnimation>(plugin:
         await plugin.managers.animation.play(params.animation.definition, params.animation.params);
         stoppedAnimation = false;
         for (let i = 0; i <= N; i++) {
-            await loop.tick(i * dt, { isSynchronous: true, manualDraw: true });
+            await loop.tick(i * dt, { isSynchronous: true, animation: { currentFrame: i, frameCount: N }, manualDraw: true });
 
             const image = params.pass.getImageData(width, height, normalizedViewport);
             encoder.addFrameRgba(image.data);

+ 7 - 7
src/mol-plugin-state/animation/built-in/camera-spin.ts

@@ -39,13 +39,9 @@ export const AnimateCameraSpin = PluginStateAnimation.create({
             return { kind: 'finished' };
         }
 
-        const phase = clamp(t.current / ctx.params.durationInMs, 0, 1);
-
-        if (phase >= 0.99999) {
-            ctx.plugin.canvas3d?.requestCameraReset({ snapshot, durationMs: 0 });
-            return { kind: 'finished' };
-        }
-
+        const phase = t.animation
+            ? t.animation?.currentFrame / (t.animation.frameCount + 1)
+            : clamp(t.current / ctx.params.durationInMs, 0, 1);
         const angle = 2 * Math.PI * phase * ctx.params.speed * (ctx.params.direction === 'ccw' ? -1 : 1);
 
         Vec3.sub(_dir, snapshot.position, snapshot.target);
@@ -55,6 +51,10 @@ export const AnimateCameraSpin = PluginStateAnimation.create({
         const position = Vec3.add(Vec3(), snapshot.target, _dir);
         ctx.plugin.canvas3d?.requestCameraReset({ snapshot: { ...snapshot, position }, durationMs: 0 });
 
+        if (phase >= 0.99999) {
+            return { kind: 'finished' };
+        }
+
         return { kind: 'next', state: animState };
     }
 });

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

@@ -51,7 +51,8 @@ namespace PluginStateAnimation {
 
     export interface Time {
         lastApplied: number,
-        current: number
+        current: number,
+        animation?: { currentFrame: number, frameCount: number }
     }
 
     export type ApplyResult<S> = { kind: 'finished' } | { kind: 'skip' } | { kind: 'next', state: S }

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

@@ -99,12 +99,12 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
         await this.start();
     }
 
-    async tick(t: number, isSynchronous?: boolean) {
+    async tick(t: number, isSynchronous?: boolean, animation?: PluginAnimationManager.AnimationInfo) {
         this.currentTime = t;
         if (this.isStopped) return;
 
-        if (isSynchronous) {
-            await this.applyFrame();
+        if (isSynchronous || animation) {
+            await this.applyFrame(animation);
         } else {
             this.applyAsync();
         }
@@ -165,12 +165,12 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
         }
     }
 
-    private async applyFrame() {
+    private async applyFrame(animation?: PluginAnimationManager.AnimationInfo) {
         const t = this.currentTime;
         if (this._current.startedTime < 0) this._current.startedTime = t;
         const newState = await this._current.anim.apply(
             this._current.state,
-            { lastApplied: this._current.lastTime, current: t - this._current.startedTime },
+            { lastApplied: this._current.lastTime, current: t - this._current.startedTime, animation },
             { params: this._current.paramValues, plugin: this.context });
 
         if (newState.kind === 'finished') {
@@ -228,6 +228,11 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
 }
 
 namespace PluginAnimationManager {
+    export interface AnimationInfo {
+        currentFrame: number,
+        frameCount: number
+    }
+
     export interface Current {
         anim: PluginStateAnimation
         params: PD.Params,

+ 3 - 2
src/mol-plugin/animation-loop.ts

@@ -6,6 +6,7 @@
 
 import { PluginContext } from './context';
 import { now } from '../mol-util/now';
+import { PluginAnimationManager } from '../mol-plugin-state/manager/animation';
 
 export class PluginAnimationLoop {
     private currentFrame: any = void 0;
@@ -15,8 +16,8 @@ export class PluginAnimationLoop {
         return this._isAnimating;
     }
 
-    async tick(t: number, options?: { isSynchronous?: boolean, manualDraw?: boolean }) {
-        await this.plugin.managers.animation.tick(t, options?.isSynchronous);
+    async tick(t: number, options?: { isSynchronous?: boolean, manualDraw?: boolean, animation?: PluginAnimationManager.AnimationInfo }) {
+        await this.plugin.managers.animation.tick(t, options?.isSynchronous, options?.animation);
         this.plugin.canvas3d?.tick(t as now.Timestamp, options);
     }