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

added transition duration effect to camera focus

David Sehnal 5 éve
szülő
commit
0eacdfca85

+ 18 - 8
src/mol-canvas3d/camera.ts

@@ -6,10 +6,12 @@
  */
 
 import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra'
-import { Viewport, cameraProject, cameraUnproject } from './camera/util';
+import { Viewport, cameraProject, cameraUnproject, cameraSetClipping } from './camera/util';
 import { Object3D } from '../mol-gl/object3d';
 import { BehaviorSubject } from 'rxjs';
 import { CameraTransitionManager } from './camera/transition';
+import { Canvas3DProps } from './canvas3d';
+import Scene from '../mol-gl/scene';
 
 export { Camera }
 
@@ -80,8 +82,8 @@ class Camera implements Object3D {
         return changed;
     }
 
-    setState(snapshot: Partial<Camera.Snapshot>) {
-        this.transition.apply(snapshot);
+    setState(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
+        this.transition.apply(snapshot, durationMs);
     }
 
     getSnapshot() {
@@ -100,11 +102,17 @@ class Camera implements Object3D {
         Vec3.setMagnitude(this.deltaDirection, this.state.direction, targetDistance)
         Vec3.sub(this.newPosition, target, this.deltaDirection)
 
-        return { target: Vec3.clone(target), position: Vec3.clone(this.newPosition) };
+        const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
+        state.target = Vec3.clone(target);
+        state.position = Vec3.clone(this.newPosition);
+
+        cameraSetClipping(state, this.scene.boundingSphere, this.canvasProps)
+
+        return state;
     }
 
-    focus(target: Vec3, radius: number) {
-        if (radius > 0) this.setState(this.getFocus(target, radius));
+    focus(target: Vec3, radius: number, durationMs?: number) {
+        if (radius > 0) this.setState(this.getFocus(target, radius), durationMs);
     }
 
     // lookAt(target: Vec3) {
@@ -128,7 +136,7 @@ class Camera implements Object3D {
         this.updatedViewProjection.complete();
     }
 
-    constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
+    constructor(private scene: Scene, private canvasProps: Canvas3DProps, state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
         this.viewport = viewport;
         Camera.copySnapshot(this.state, state);
     }
@@ -208,7 +216,7 @@ namespace Camera {
     }
 
     export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) {
-        if (!source) return;
+        if (!source) return out;
 
         if (typeof source.mode !== 'undefined') out.mode = source.mode;
 
@@ -224,6 +232,8 @@ namespace Camera {
 
         if (typeof source.fov !== 'undefined') out.fov = source.fov;
         if (typeof source.zoom !== 'undefined') out.zoom = source.zoom;
+
+        return out;
     }
 }
 

+ 1 - 1
src/mol-canvas3d/camera/transition.ts

@@ -22,7 +22,7 @@ class CameraTransitionManager {
     private current = Camera.createDefaultSnapshot();
 
     apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
-        if (durationMs <= 0 || to.mode !== this.camera.state.mode) {
+        if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
             this.finish(to);
             return;
         }

+ 40 - 0
src/mol-canvas3d/camera/util.ts

@@ -5,6 +5,9 @@
  */
 
 import { Mat4, Vec3, Vec4, EPSILON } from '../../mol-math/linear-algebra'
+import { Camera } from '../camera'
+import { Sphere3D } from '../../mol-math/geometry'
+import { Canvas3DProps } from '../canvas3d'
 
 export { Viewport }
 
@@ -80,6 +83,43 @@ export function cameraLookAt(position: Vec3, up: Vec3, direction: Vec3, target:
     }
 }
 
+export function cameraSetClipping(state: Camera.Snapshot, boundingSphere: Sphere3D, p: Canvas3DProps) {
+    const cDist = Vec3.distance(state.position, state.target)
+    const bRadius = Math.max(10, boundingSphere.radius)
+
+    const nearFactor = (50 - p.clip[0]) / 50
+    const farFactor = -(50 - p.clip[1]) / 50
+    let near = cDist - (bRadius * nearFactor)
+    let far = cDist + (bRadius * farFactor)
+
+    const fogNearFactor = (50 - p.fog[0]) / 50
+    const fogFarFactor = -(50 - p.fog[1]) / 50
+    let fogNear = cDist - (bRadius * fogNearFactor)
+    let fogFar = cDist + (bRadius * fogFarFactor)
+
+    if (state.mode === 'perspective') {
+        // set at least to 5 to avoid slow sphere impostor rendering
+        near = Math.max(5, p.cameraClipDistance, near)
+        far = Math.max(5, far)
+        fogNear = Math.max(5, fogNear)
+        fogFar = Math.max(5, fogFar)
+    } else if (state.mode === 'orthographic') {
+        if (p.cameraClipDistance > 0) {
+            near = Math.max(p.cameraClipDistance, near)
+        }
+    }
+
+    state.near = near;
+    state.far = far;
+    state.fogNear = fogNear;
+    state.fogFar = fogFar;
+
+    // if (near !== currentNear || far !== currentFar || fogNear !== currentFogNear || fogFar !== currentFogFar) {
+    //     camera.setState({ near, far, fogNear, fogFar })
+    //     currentNear = near, currentFar = far, currentFogNear = fogNear, currentFogFar = fogFar
+    // }
+}
+
 const NEAR_RANGE = 0
 const FAR_RANGE = 1
 

+ 44 - 39
src/mol-canvas3d/canvas3d.ts

@@ -11,7 +11,7 @@ import InputObserver, { ModifiersKeys, ButtonsType } from '../mol-util/input/inp
 import Renderer, { RendererStats, RendererParams } from '../mol-gl/renderer'
 import { GraphicsRenderObject } from '../mol-gl/render-object'
 import { TrackballControls, TrackballControlsParams } from './controls/trackball'
-import { Viewport } from './camera/util'
+import { Viewport, cameraSetClipping } from './camera/util'
 import { createContext, WebGLContext, getGLContext } from '../mol-gl/webgl/context';
 import { Representation } from '../mol-repr/representation';
 import Scene from '../mol-gl/scene';
@@ -38,6 +38,7 @@ export const Canvas3DParams = {
     // maxFps: PD.Numeric(30),
     cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
     cameraClipDistance: PD.Numeric(0, { min: 0.0, max: 50.0, step: 0.1 }, { description: 'The distance between camera and scene at which to clip regardless of near clipping plane.' }),
+    cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
     clip: PD.Interval([1, 100], { min: 1, max: 100, step: 1 }),
     fog: PD.Interval([50, 100], { min: 1, max: 100, step: 1 }),
 
@@ -116,19 +117,20 @@ namespace Canvas3D {
         const startTime = now()
         const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
 
-        const camera = new Camera({
-            near: 0.1,
-            far: 10000,
-            position: Vec3.create(0, 0, 100),
-            mode: p.cameraMode
-        })
-
         const webgl = createContext(gl)
 
         let width = gl.drawingBufferWidth
         let height = gl.drawingBufferHeight
 
         const scene = Scene.create(webgl)
+
+        const camera = new Camera(scene, p, {
+            near: 0.1,
+            far: 10000,
+            position: Vec3.create(0, 0, 100),
+            mode: p.cameraMode
+        })
+
         const controls = TrackballControls.create(input, camera, p.trackball)
         const renderer = Renderer.create(webgl, camera, p.renderer)
         const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
@@ -172,37 +174,38 @@ namespace Canvas3D {
             }
         }
 
-        let currentNear = -1, currentFar = -1, currentFogNear = -1, currentFogFar = -1
+        // let currentNear = -1, currentFar = -1, currentFogNear = -1, currentFogFar = -1
         function setClipping() {
-            const cDist = Vec3.distance(camera.state.position, camera.state.target)
-            const bRadius = Math.max(10, scene.boundingSphere.radius)
-
-            const nearFactor = (50 - p.clip[0]) / 50
-            const farFactor = -(50 - p.clip[1]) / 50
-            let near = cDist - (bRadius * nearFactor)
-            let far = cDist + (bRadius * farFactor)
-
-            const fogNearFactor = (50 - p.fog[0]) / 50
-            const fogFarFactor = -(50 - p.fog[1]) / 50
-            let fogNear = cDist - (bRadius * fogNearFactor)
-            let fogFar = cDist + (bRadius * fogFarFactor)
-
-            if (camera.state.mode === 'perspective') {
-                // set at least to 5 to avoid slow sphere impostor rendering
-                near = Math.max(5, p.cameraClipDistance, near)
-                far = Math.max(5, far)
-                fogNear = Math.max(5, fogNear)
-                fogFar = Math.max(5, fogFar)
-            } else if (camera.state.mode === 'orthographic') {
-                if (p.cameraClipDistance > 0) {
-                    near = Math.max(p.cameraClipDistance, near)
-                }
-            }
-
-            if (near !== currentNear || far !== currentFar || fogNear !== currentFogNear || fogFar !== currentFogFar) {
-                camera.setState({ near, far, fogNear, fogFar })
-                currentNear = near, currentFar = far, currentFogNear = fogNear, currentFogFar = fogFar
-            }
+            cameraSetClipping(camera.state, scene.boundingSphere, p);
+            // const cDist = Vec3.distance(camera.state.position, camera.state.target)
+            // const bRadius = Math.max(10, scene.boundingSphere.radius)
+
+            // const nearFactor = (50 - p.clip[0]) / 50
+            // const farFactor = -(50 - p.clip[1]) / 50
+            // let near = cDist - (bRadius * nearFactor)
+            // let far = cDist + (bRadius * farFactor)
+
+            // const fogNearFactor = (50 - p.fog[0]) / 50
+            // const fogFarFactor = -(50 - p.fog[1]) / 50
+            // let fogNear = cDist - (bRadius * fogNearFactor)
+            // let fogFar = cDist + (bRadius * fogFarFactor)
+
+            // if (camera.state.mode === 'perspective') {
+            //     // set at least to 5 to avoid slow sphere impostor rendering
+            //     near = Math.max(5, p.cameraClipDistance, near)
+            //     far = Math.max(5, far)
+            //     fogNear = Math.max(5, fogNear)
+            //     fogFar = Math.max(5, fogFar)
+            // } else if (camera.state.mode === 'orthographic') {
+            //     if (p.cameraClipDistance > 0) {
+            //         near = Math.max(p.cameraClipDistance, near)
+            //     }
+            // }
+
+            // if (near !== currentNear || far !== currentFar || fogNear !== currentFogNear || fogFar !== currentFogFar) {
+            //     camera.setState({ near, far, fogNear, fogFar })
+            //     currentNear = near, currentFar = far, currentFogNear = fogNear, currentFogFar = fogFar
+            // }
         }
 
         function render(variant: 'pick' | 'draw', force: boolean) {
@@ -348,7 +351,7 @@ namespace Canvas3D {
                 if (scene.isCommiting) {
                     cameraResetRequested = true
                 } else {
-                    camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
+                    camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, p.cameraResetDurationMs)
                     requestDraw(true);
                 }
             },
@@ -372,6 +375,7 @@ namespace Canvas3D {
                     camera.setState({ mode: props.cameraMode })
                 }
                 if (props.cameraClipDistance !== undefined) p.cameraClipDistance = props.cameraClipDistance
+                if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
                 if (props.clip !== undefined) p.clip = [props.clip[0], props.clip[1]]
                 if (props.fog !== undefined) p.fog = [props.fog[0], props.fog[1]]
 
@@ -387,6 +391,7 @@ namespace Canvas3D {
                 return {
                     cameraMode: camera.state.mode,
                     cameraClipDistance: p.cameraClipDistance,
+                    cameraResetDurationMs: p.cameraResetDurationMs,
                     clip: p.clip,
                     fog: p.fog,
 

+ 2 - 1
src/mol-gl/_spec/renderer.spec.ts

@@ -24,10 +24,11 @@ import { Color } from '../../mol-util/color';
 import { Sphere3D } from '../../mol-math/geometry';
 import { createEmptyOverpaint } from '../../mol-geo/geometry/overpaint-data';
 import { createEmptyTransparency } from '../../mol-geo/geometry/transparency-data';
+import { DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
 
 function createRenderer(gl: WebGLRenderingContext) {
     const ctx = createContext(gl)
-    const camera = new Camera({
+    const camera = new Camera(Scene.create(ctx), DefaultCanvas3DParams, {
         near: 0.01,
         far: 10000,
         position: Vec3.create(0, 0, 50)

+ 5 - 4
src/mol-plugin/behavior/dynamic/camera.ts

@@ -9,23 +9,24 @@ import { ParamDefinition } from '../../../mol-util/param-definition';
 import { PluginBehavior } from '../behavior';
 import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
 
-export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extraRadius: number }>({
+export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extraRadius: number, durationMs?: number }>({
     name: 'focus-loci-on-select',
     category: 'interaction',
-    ctor: class extends PluginBehavior.Handler<{ minRadius: number, extraRadius: number }> {
+    ctor: class extends PluginBehavior.Handler<{ minRadius: number, extraRadius: number, durationMs?: number }> {
         register(): void {
             this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
                 if (!this.ctx.canvas3d || buttons !== ButtonsType.Flag.Primary || !ModifiersKeys.areEqual(modifiers, ModifiersKeys.None)) return;
 
                 const sphere = Loci.getBoundingSphere(current.loci);
                 if (!sphere) return;
-                this.ctx.canvas3d.camera.focus(sphere.center, Math.max(sphere.radius + this.params.extraRadius, this.params.minRadius));
+                this.ctx.canvas3d.camera.focus(sphere.center, Math.max(sphere.radius + this.params.extraRadius, this.params.minRadius), this.params.durationMs);
             });
         }
     },
     params: () => ({
         minRadius: ParamDefinition.Numeric(8, { min: 1, max: 50, step: 1 }),
-        extraRadius: ParamDefinition.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci.' })
+        extraRadius: ParamDefinition.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci.' }),
+        durationMs: ParamDefinition.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'Camera transition duration.' })
     }),
     display: { name: 'Focus Loci on Select' }
 });

+ 1 - 1
src/mol-plugin/index.ts

@@ -63,7 +63,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci),
         PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci),
         PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
-        PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 8, extraRadius: 4 }),
+        PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 8, extraRadius: 4, durationMs: 250 }),
         // PluginSpec.Behavior(PluginBehaviors.Labels.SceneLabels),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.MolstarSecondaryStructure, { autoAttach: true }),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true, showTooltip: true }),