diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts index f2e482abc163884b35a6565ec0d160c8de177b67..38d77bef246faf01e48f20b8c1e2cce288cb0aec 100644 --- a/src/mol-canvas3d/camera.ts +++ b/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, diff --git a/src/mol-canvas3d/camera/transition.ts b/src/mol-canvas3d/camera/transition.ts new file mode 100644 index 0000000000000000000000000000000000000000..71f27c649c565876e71c8293349883166fb457b5 --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index e4bb91a6478cdd47c652ba66c949f382b3e41d0f..c884fc4fdf7f653a496510db7b205f8c8604ec37 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/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) diff --git a/src/mol-math/linear-algebra/3d/quat.ts b/src/mol-math/linear-algebra/3d/quat.ts index a758c175990a621d5b541095abab51026cc35a43..119126fc2b19535c5b881f71e579d0db7f17043d 100644 --- a/src/mol-math/linear-algebra/3d/quat.ts +++ b/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; diff --git a/src/mol-plugin/behavior/static/camera.ts b/src/mol-plugin/behavior/static/camera.ts index 47394e2932a51c8e5a7800d55c51efc343504db9..d1e68ac6987115382c5ec01fd68e8433a0484c38 100644 --- a/src/mol-plugin/behavior/static/camera.ts +++ b/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 }); }); } \ No newline at end of file diff --git a/src/mol-plugin/command/camera.ts b/src/mol-plugin/command/camera.ts index 0020d13354819b81f82013011e4c26e4f455312a..b5138f9d66519b0ef0beb33447641d3db22cdb7d 100644 --- a/src/mol-plugin/command/camera.ts +++ b/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 }),