Browse Source

add rock animation to trackball controls

Alexander Rose 3 years ago
parent
commit
796a034fec

+ 3 - 0
CHANGELOG.md

@@ -10,6 +10,9 @@ Note that since we don't clearly distinguish between a public and private interf
 - Move Viewer APP to a separate file to allow use without importing light theme & index.html
 - Add symmetry support for mol2 files (only spacegroup setting 1)
 - 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
 

+ 11 - 3
src/examples/basic-wrapper/index.ts

@@ -74,12 +74,20 @@ class BasicWrapper {
     toggleSpin() {
         if (!this.plugin.canvas3d) return;
 
+        const trackball = this.plugin.canvas3d.props.trackball;
         PluginCommands.Canvas3D.SetSettings(this.plugin, {
-            settings: props => {
-                props.trackball.spin = !props.trackball.spin;
+            settings: {
+                trackball: {
+                    ...trackball,
+                    animate: trackball.animate.name === 'spin'
+                        ? { name: 'off', params: {} }
+                        : { name: 'spin', params: { speed: 1 } }
+                }
             }
         });
-        if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
+        if (this.plugin.canvas3d.props.trackball.animate.name !== 'spin') {
+            PluginCommands.Camera.Reset(this.plugin, {});
+        }
     }
 
     private animateModelIndexTargetFps() {

+ 10 - 1
src/examples/proteopedia-wrapper/index.ts

@@ -256,7 +256,16 @@ class MolStarProteopediaWrapper {
     toggleSpin() {
         if (!this.plugin.canvas3d) return;
         const trackball = this.plugin.canvas3d.props.trackball;
-        PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
+        PluginCommands.Canvas3D.SetSettings(this.plugin, {
+            settings: {
+                trackball: {
+                    ...trackball,
+                    animate: trackball.animate.name === 'spin'
+                        ? { name: 'off', params: {} }
+                        : { name: 'spin', params: { speed: 1 } }
+                }
+            }
+        });
     }
 
     viewport = {

+ 2 - 2
src/mol-canvas3d/canvas3d.ts

@@ -404,7 +404,7 @@ namespace Canvas3D {
 
                 const ctx = { renderer, camera: cam, scene, helper };
                 if (MultiSamplePass.isEnabled(p.multiSample)) {
-                    const forceOn = !cameraChanged && allowMulti && !controls.props.spin;
+                    const forceOn = !cameraChanged && allowMulti && !controls.isAnimating;
                     multiSampleHelper.render(ctx, p, true, forceOn);
                 } else {
                     passes.draw.render(ctx, p, true);
@@ -444,7 +444,7 @@ namespace Canvas3D {
             }
 
             draw();
-            if (!camera.transition.inTransition && !controls.props.spin && !webgl.isContextLost) {
+            if (!camera.transition.inTransition && !controls.isAnimating && !webgl.isContextLost) {
                 interactionHelper.tick(currentTime);
             }
         }

+ 59 - 10
src/mol-canvas3d/controls/trackball.ts

@@ -13,8 +13,9 @@ import { Viewport } from '../camera/util';
 import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys, GestureInput } from '../../mol-util/input/input-observer';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Camera } from '../camera';
-import { absMax } from '../../mol-math/misc';
+import { absMax, degToRad } from '../../mol-math/misc';
 import { Binding } from '../../mol-util/binding';
+import { smoothstep } from '../../mol-math/interpolate';
 
 const B = ButtonsType;
 const M = ModifiersKeys;
@@ -40,8 +41,16 @@ export const TrackballControlsParams = {
     zoomSpeed: PD.Numeric(7.0, { min: 1, max: 15, step: 1 }),
     panSpeed: PD.Numeric(1.0, { min: 0.1, max: 5, step: 0.1 }),
 
-    spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
-    spinSpeed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
+    animate: PD.MappedStatic('off', {
+        off: PD.EmptyGroup(),
+        spin: PD.Group({
+            speed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
+        }, { description: 'Spin the 3D scene around the x-axis in view space' }),
+        rock: PD.Group({
+            speed: PD.Numeric(2, { min: -20, max: 20, step: 1 }),
+            angle: PD.Numeric(10, { min: 0, max: 90, step: 1 }, { description: 'How many degrees to rotate in each direction.' }),
+        }, { description: 'Rock the 3D scene around the x-axis in view space' })
+    }),
 
     staticMoving: PD.Boolean(true, { isHidden: true }),
     dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
@@ -72,7 +81,8 @@ export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
 
 export { TrackballControls };
 interface TrackballControls {
-    viewport: Viewport
+    readonly viewport: Viewport
+    readonly isAnimating: boolean
 
     readonly props: Readonly<TrackballControlsProps>
     setProps: (props: Partial<TrackballControlsProps>) => void
@@ -144,6 +154,11 @@ namespace TrackballControls {
             );
         }
 
+        function getRotateFactor() {
+            const aspectRatio = input.width / input.height;
+            return p.rotateSpeed * input.pixelRatio * aspectRatio;
+        }
+
         const rotAxis = Vec3();
         const rotQuat = Quat();
         const rotEyeDir = Vec3();
@@ -156,8 +171,7 @@ namespace TrackballControls {
             const dy = _rotCurr[1] - _rotPrev[1];
             Vec3.set(rotMoveDir, dx, dy, 0);
 
-            const aspectRatio = input.width / input.height;
-            const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio * aspectRatio;
+            const angle = Vec3.magnitude(rotMoveDir) * getRotateFactor();
 
             if (angle) {
                 Vec3.sub(_eye, camera.position, camera.target);
@@ -306,7 +320,10 @@ namespace TrackballControls {
         /** Update the object's position, direction and up vectors */
         function update(t: number) {
             if (lastUpdated === t) return;
-            if (p.spin && lastUpdated > 0) spin(t - lastUpdated);
+            if (lastUpdated > 0) {
+                if (p.animate.name === 'spin') spin(t - lastUpdated);
+                else if (p.animate.name === 'rock') rock(t - lastUpdated);
+            }
 
             Vec3.sub(_eye, camera.position, camera.target);
 
@@ -345,6 +362,7 @@ namespace TrackballControls {
             if (!isStart && !_isInteracting) return;
 
             _isInteracting = true;
+            resetRock(); // start rocking from the center after interactions
 
             const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers);
             const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers);
@@ -434,11 +452,38 @@ namespace TrackballControls {
 
         const _spinSpeed = Vec2.create(0.005, 0);
         function spin(deltaT: number) {
-            if (p.spinSpeed === 0) return;
+            if (p.animate.name !== 'spin' || p.animate.params.speed === 0 || _isInteracting) return;
 
-            const frameSpeed = (p.spinSpeed || 0) / 1000;
+            const frameSpeed = p.animate.params.speed / 1000;
             _spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
-            if (!_isInteracting) Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
+            Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
+        }
+
+        let _rockAngleSum = 0;
+        let _rockDirection = 1;
+        const _rockSpeed = Vec2.create(0.005, 0);
+        function rock(deltaT: number) {
+            if (p.animate.name !== 'rock' || p.animate.params.speed === 0 || _isInteracting) return;
+
+            // TODO get rid of the 3.3 factor
+            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);
+            Vec2.add(_rotCurr, _rotPrev, _rockSpeed);
+
+            if (_rockAngleSum >= maxAngle) {
+                _rockDirection *= -1;
+                _rockAngleSum = -maxAngle;
+            }
+        }
+
+        function resetRock() {
+            _rockAngleSum = 0;
+            _rockDirection = 1;
         }
 
         function start(t: number) {
@@ -448,9 +493,13 @@ namespace TrackballControls {
 
         return {
             viewport,
+            get isAnimating() { return p.animate.name !== 'off'; },
 
             get props() { return p as Readonly<TrackballControlsProps>; },
             setProps: (props: Partial<TrackballControlsProps>) => {
+                if (props.animate?.name === 'rock' && p.animate.name !== 'rock') {
+                    resetRock(); // start rocking from the center
+                }
                 Object.assign(p, props);
             },
 

+ 4 - 8
src/mol-plugin-ui/viewport/simple-settings.tsx

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-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>
@@ -46,10 +46,7 @@ const LayoutOptions = {
 type LayoutOptions = keyof typeof LayoutOptions
 
 const SimpleSettingsParams = {
-    spin: PD.Group({
-        spin: Canvas3DParams.trackball.params.spin,
-        speed: Canvas3DParams.trackball.params.spinSpeed
-    }, { pivot: 'spin' }),
+    animate: Canvas3DParams.trackball.params.animate,
     camera: Canvas3DParams.camera,
     background: PD.Group({
         color: PD.Color(Color(0xFCFBF9), { label: 'Background', description: 'Custom background color' }),
@@ -96,7 +93,7 @@ const SimpleSettingsMapping = ParamMapping({
 
         return {
             layout: props.layout,
-            spin: { spin: !!canvas.trackball.spin, speed: canvas.trackball.spinSpeed },
+            animate: canvas.trackball.animate,
             camera: canvas.camera,
             background: {
                 color: renderer.backgroundColor,
@@ -114,8 +111,7 @@ const SimpleSettingsMapping = ParamMapping({
     },
     update(s, props) {
         const canvas = props.canvas as Mutable<Canvas3DProps>;
-        canvas.trackball.spin = s.spin.spin;
-        canvas.trackball.spinSpeed = s.spin.speed;
+        canvas.trackball.animate = s.animate;
         canvas.camera = s.camera;
         canvas.transparentBackground = s.background.transparent;
         canvas.renderer.backgroundColor = s.background.color;