Forráskód Böngészése

add camera rock state animation

Alexander Rose 3 éve
szülő
commit
ea5421002b

+ 4 - 3
CHANGELOG.md

@@ -11,6 +11,10 @@ Note that since we don't clearly distinguish between a public and private interf
     - Fix camera stutter for "camera spin" animation
 - Add partial charge parsing support for MOL/SDF files (thanks @ptourlas)
 - [Breaking] Cleaner looking ``MembraneOrientationVisuals`` defaults
+- [Breaking] Add rock animation to trackball controls
+    - Add ``animate`` to ``TrackballControlsParams``, remove ``spin`` and ``spinSpeed``
+    - Add ``animate`` to ``SimpleSettingsParams``, remove ``spin``
+- Add "camera rock" state animation
 
 ## [v3.0.0-dev.9] - 2022-01-09
 
@@ -22,9 +26,6 @@ Note that since we don't clearly distinguish between a public and private interf
     - Add ``key`` field for mapping to source data
     - Fix assignment of bonds with unphysical length
 - Fix label/stats of single atom selection in multi-chain units
-- [Breaking] Add rock animation to trackball controls
-    - Add ``animate`` to ``TrackballControlsParams``, remove ``spin`` and ``spinSpeed``
-    - Add ``animate`` to ``SimpleSettingsParams``, remove ``spin``
 
 ## [v3.0.0-dev.8] - 2021-12-31
 

+ 3 - 3
src/mol-canvas3d/controls/trackball.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -465,14 +465,14 @@ namespace TrackballControls {
         function rock(deltaT: number) {
             if (p.animate.name !== 'rock' || p.animate.params.speed === 0 || _isInteracting) return;
 
-            // TODO get rid of the 3.3 factor
+            // TODO get rid of the 3.3 factor (compensates for using `smoothstep`)
             const maxAngle = degToRad(p.animate.params.angle * 3.3) / getRotateFactor();
             const alpha = smoothstep(0, 1, Math.abs(_rockAngleSum) / maxAngle);
 
             const frameSpeed = p.animate.params.speed / 1000;
             _rockSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
             _rockAngleSum += Math.abs(_rockSpeed[0]);
-            _rockSpeed[0] = _rockSpeed[0] * _rockDirection * (1.1 - alpha);
+            _rockSpeed[0] *= _rockDirection * (1.1 - alpha);
             Vec2.add(_rotCurr, _rotPrev, _rockSpeed);
 
             if (_rockAngleSum >= maxAngle) {

+ 97 - 0
src/mol-plugin-state/animation/built-in/camera-rock.ts

@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Camera } from '../../../mol-canvas3d/camera';
+import { clamp, smoothstep } from '../../../mol-math/interpolate';
+import { Quat } from '../../../mol-math/linear-algebra/3d/quat';
+import { Vec3 } from '../../../mol-math/linear-algebra/3d/vec3';
+import { degToRad } from '../../../mol-math/misc';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { PluginStateAnimation } from '../model';
+
+const _dir = Vec3(), _axis = Vec3(), _rot = Quat();
+
+type State = {
+    snapshot: Camera.Snapshot,
+    angles: number[]
+};
+
+export const AnimateCameraRock = PluginStateAnimation.create({
+    name: 'built-in.animate-camera-rock',
+    display: { name: 'Camera Rock', description: 'Rock the 3D scene around the x-axis in view space' },
+    isExportable: true,
+    params: () => ({
+        durationInMs: PD.Numeric(4000, { min: 100, max: 20000, step: 100 }),
+        angle: PD.Numeric(10, { min: 0, max: 90, step: 1 }, { description: 'How many degrees to rotate in each direction.' }),
+    }),
+    initialState: (p, ctx) => {
+        const angles: number[] = [];
+        const frameSpeed = 1 / 1000;
+        const deltaT = 1000 / 30;
+
+        // TODO get rid of the 3.3 factor (compensates for using `smoothstep`)
+        const maxAngle = degToRad(p.angle * 3.3);
+
+        let angleSum = 0;
+        let direction = 1;
+        let zeroAngleCount = 0;
+        let prevAngle = 0;
+        while (true) {
+            const alpha = smoothstep(0, 1, Math.abs(angleSum) / maxAngle);
+            const rockSpeed = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
+            angleSum += Math.abs(rockSpeed);
+            const angle = prevAngle + rockSpeed * direction * (1.1 - alpha);
+            angles.push(angle);
+            if (Math.sign(prevAngle) !== Math.sign(angle)) {
+                zeroAngleCount += 1;
+                if (zeroAngleCount === 3) break;
+            }
+            prevAngle = angle;
+            if (angleSum >= maxAngle) {
+                direction *= -1;
+                angleSum = -maxAngle;
+            }
+        }
+
+        return {
+            snapshot: ctx.canvas3d!.camera.getSnapshot(),
+            angles
+        } as State;
+    },
+    getDuration: p => ({ kind: 'fixed', durationMs: p.durationInMs }),
+    teardown: (_, state: State, ctx) => {
+        ctx.canvas3d?.requestCameraReset({ snapshot: state.snapshot, durationMs: 0 });
+    },
+
+    async apply(animState: State, t, ctx) {
+        if (t.current === 0) {
+            return { kind: 'next', state: animState };
+        }
+
+        const snapshot = animState.snapshot;
+        if (snapshot.radiusMax < 0.0001) {
+            return { kind: 'finished' };
+        }
+
+        const phase = t.animation
+            ? t.animation?.currentFrame / (t.animation.frameCount + 1)
+            : clamp(t.current / ctx.params.durationInMs, 0, 1);
+        const angle = animState.angles[Math.round(phase * (animState.angles.length - 1))];
+
+        Vec3.sub(_dir, snapshot.position, snapshot.target);
+        Vec3.normalize(_axis, snapshot.up);
+        Quat.setAxisAngle(_rot, _axis, angle);
+        Vec3.transformQuat(_dir, _dir, _rot);
+        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 };
+    }
+});

+ 3 - 1
src/mol-plugin/spec.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -23,6 +23,7 @@ import { StateTransforms } from '../mol-plugin-state/transforms';
 import { BoxifyVolumeStreaming, CreateVolumeStreamingBehavior, InitVolumeStreaming } from '../mol-plugin/behavior/dynamic/volume-streaming/transformers';
 import { AnimateStateInterpolation } from '../mol-plugin-state/animation/built-in/state-interpolation';
 import { AnimateStructureSpin } from '../mol-plugin-state/animation/built-in/spin-structure';
+import { AnimateCameraRock } from '../mol-plugin-state/animation/built-in/camera-rock';
 
 export { PluginSpec };
 
@@ -131,6 +132,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
     animations: [
         AnimateModelIndex,
         AnimateCameraSpin,
+        AnimateCameraRock,
         AnimateStateSnapshots,
         AnimateAssemblyUnwind,
         AnimateStructureSpin,