Explorar o código

camera transitions

David Sehnal %!s(int64=6) %!d(string=hai) anos
pai
achega
50525796cd

+ 14 - 9
src/mol-canvas3d/camera.ts

@@ -6,9 +6,10 @@
  */
 
 import { Mat4, Vec3, Vec4, EPSILON } from 'mol-math/linear-algebra'
-import { Viewport, cameraLookAt, cameraProject, cameraUnproject } from './camera/util';
+import { Viewport, cameraProject, cameraUnproject } from './camera/util';
 import { Object3D } from 'mol-gl/object3d';
 import { BehaviorSubject } from 'rxjs';
+import { CameraTransitionManager } from './camera/transition';
 
 export { Camera }
 
@@ -25,6 +26,8 @@ class Camera implements Object3D {
     readonly viewport: Viewport;
     readonly state: Readonly<Camera.Snapshot> = Camera.createDefaultSnapshot();
 
+    readonly transition: CameraTransitionManager = new CameraTransitionManager(this);
+
     get position() { return this.state.position; }
     set position(v: Vec3) { Vec3.copy(this.state.position, v); }
 
@@ -69,8 +72,8 @@ class Camera implements Object3D {
         return changed;
     }
 
-    setState(snapshot?: Partial<Camera.Snapshot>) {
-        Camera.copySnapshot(this.state, snapshot);
+    setState(snapshot: Partial<Camera.Snapshot>) {
+        this.transition.apply(snapshot);
     }
 
     getSnapshot() {
@@ -79,13 +82,14 @@ class Camera implements Object3D {
         return ret;
     }
 
-    lookAt(target: Vec3) {
-        cameraLookAt(this.direction, this.up, this.position, target);
-    }
+    // lookAt(target: Vec3) {
+    //     cameraLookAt(this.position, this.up, this.direction, target);
+    // }
 
-    translate(v: Vec3) {
-        Vec3.add(this.position, this.position, v)
-    }
+    // translate(v: Vec3) {
+    //     Vec3.add(this.position, this.position, v);
+    //     cameraLookAt(this.position, this.up, this.direction, this.target);
+    // }
 
     project(out: Vec4, point: Vec3) {
         return cameraProject(out, point, this.viewport, this.projectionView)
@@ -133,6 +137,7 @@ namespace Camera {
         mode: Mode,
 
         position: Vec3,
+        // Normalized camera direction
         direction: Vec3,
         up: Vec3,
         target: Vec3,

+ 100 - 0
src/mol-canvas3d/camera/transition.ts

@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Camera } from 'mol-canvas3d/camera';
+import { Quat, Vec3 } from 'mol-math/linear-algebra';
+import { lerp } from 'mol-math/interpolate';
+
+export { CameraTransitionManager }
+
+class CameraTransitionManager {
+    private t = 0;
+
+    private func: CameraTransitionManager.TransitionFunc = CameraTransitionManager.defaultTransition;
+    private start = 0;
+    private inTransition = false;
+    private durationMs = 0;
+    private source: Camera.Snapshot = Camera.createDefaultSnapshot();
+    private target: Camera.Snapshot = Camera.createDefaultSnapshot();
+    private current = Camera.createDefaultSnapshot();
+
+    apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
+        if (durationMs <= 0 || to.mode !== this.camera.state.mode) {
+            this.finish(to);
+            return;
+        }
+
+        Camera.copySnapshot(this.source, this.camera);
+        Camera.copySnapshot(this.target, this.camera);
+        Camera.copySnapshot(this.target, to);
+
+        this.inTransition = true;
+        this.func = transition || CameraTransitionManager.defaultTransition;
+        this.start = this.t;
+        this.durationMs = durationMs;
+    }
+
+    tick(t: number) {
+        this.t = t;
+        this.update();
+    }
+
+    private finish(to: Partial<Camera.Snapshot>) {
+        Camera.copySnapshot(this.camera.state, to);
+        this.inTransition = false;
+    }
+
+    private update() {
+        if (!this.inTransition) return;
+
+        const normalized = Math.min((this.t - this.start) / this.durationMs, 1);
+        if (normalized === 1) {
+            this.finish(this.target!);
+            return;
+        }
+
+        this.func(this.current, normalized, this.source, this.target);
+        Camera.copySnapshot(this.camera.state, this.current);
+    }
+
+    constructor(private camera: Camera) {
+
+    }
+}
+
+namespace CameraTransitionManager {
+    export type TransitionFunc = (out: Camera.Snapshot, t: number, source: Camera.Snapshot, target: Camera.Snapshot) => void
+
+    export function defaultTransition(out: Camera.Snapshot, t: number, source: Camera.Snapshot, target: Camera.Snapshot): void {
+        Camera.copySnapshot(out, target);
+
+        // Rotate direction
+        const rot = Quat.identity();
+        Quat.slerp(rot, rot, Quat.rotationTo(Quat.zero(), source.direction, target.direction), t);
+        Vec3.transformQuat(out.direction, source.direction, rot);
+
+        // Rotate up
+        Quat.setIdentity(rot);
+        Quat.slerp(rot, rot, Quat.rotationTo(Quat.zero(), source.up, target.up), t);
+        Vec3.transformQuat(out.up, source.up, rot);
+
+        // Lerp target
+        Vec3.lerp(out.target, source.target, target.target, t);
+
+        // Update position
+        const dist = -lerp(Vec3.distance(source.position, source.target), Vec3.distance(target.position, target.target), t);
+        Vec3.scale(out.position, out.direction, dist);
+        Vec3.add(out.position, out.position, out.target);
+
+        // Lerp other props
+        out.zoom = lerp(source.zoom, target.zoom, t);
+        out.fov = lerp(source.fov, target.fov, t);
+        out.near = lerp(source.near, target.near, t);
+        out.far = lerp(source.far, target.far, t);
+        out.fogNear = lerp(source.fogNear, target.fogNear, t);
+        out.fogFar = lerp(source.fogFar, target.fogFar, t);
+    }
+}

+ 3 - 1
src/mol-canvas3d/canvas3d.ts

@@ -225,8 +225,10 @@ namespace Canvas3D {
         }
 
         function animate() {
+            const t = now();
+            camera.transition.tick(t);
             draw(false)
-            if (now() - lastRenderTime > 200) {
+            if (t - lastRenderTime > 200) {
                 if (pickDirty) pick()
             }
             window.requestAnimationFrame(animate)

+ 7 - 0
src/mol-math/linear-algebra/3d/quat.ts

@@ -46,6 +46,13 @@ namespace Quat {
         return out;
     }
 
+    export function setIdentity(out: Quat) {
+        out[0] = 0;
+        out[1] = 0;
+        out[2] = 0;
+        out[3] = 1;
+    }
+
     export function create(x: number, y: number, z: number, w: number) {
         const out = identity();
         out[0] = x;

+ 3 - 4
src/mol-plugin/behavior/static/camera.ts

@@ -30,9 +30,8 @@ export function Reset(ctx: PluginContext) {
 }
 
 export function SetSnapshot(ctx: PluginContext) {
-    PluginCommands.Camera.SetSnapshot.subscribe(ctx, ({ snapshot }) => {
-        ctx.canvas3d.camera.setState(snapshot);
-        ctx.canvas3d.requestDraw();
+    PluginCommands.Camera.SetSnapshot.subscribe(ctx, ({ snapshot, durationMs }) => {
+        ctx.canvas3d.camera.transition.apply(snapshot, durationMs);
     })
 }
 
@@ -52,6 +51,6 @@ export function Snapshots(ctx: PluginContext) {
 
     PluginCommands.Camera.Snapshots.Apply.subscribe(ctx, ({ id }) => {
         const e = ctx.state.cameraSnapshots.getEntry(id);
-        return PluginCommands.Camera.SetSnapshot.dispatch(ctx, { snapshot: e.snapshot });
+        return PluginCommands.Camera.SetSnapshot.dispatch(ctx, { snapshot: e.snapshot, durationMs: 500 });
     });
 }

+ 1 - 1
src/mol-plugin/command/camera.ts

@@ -8,7 +8,7 @@ import { PluginCommand } from './command';
 import { Camera } from 'mol-canvas3d/camera';
 
 export const Reset = PluginCommand<{}>({ isImmediate: true });
-export const SetSnapshot = PluginCommand<{ snapshot: Camera.Snapshot }>({ isImmediate: true });
+export const SetSnapshot = PluginCommand<{ snapshot: Camera.Snapshot, durationMs?: number }>({ isImmediate: true });
 
 export const Snapshots = {
     Add: PluginCommand<{ name?: string, description?: string }>({ isImmediate: true }),