diff --git a/src/apps/viewer/extensions/jolecule.ts b/src/apps/viewer/extensions/jolecule.ts index b4c22aa00bc51b9a7ac9a8557d5c40d6e8c975de..de4f1733f7fc8a4dbfee4c0b5a5c555a9d5f813e 100644 --- a/src/apps/viewer/extensions/jolecule.ts +++ b/src/apps/viewer/extensions/jolecule.ts @@ -147,6 +147,7 @@ function getCameraSnapshot(e: JoleculeSnapshot['camera']): Camera.Snapshot { mode: 'perspective', position: Vec3.scaleAndAdd(Vec3.zero(), e.pos, direction, e.slab.zoom), target: e.pos, + radius: e.slab.zoom, direction, up, near: e.slab.zoom + e.slab.z_front, diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts index 7076a1a899a6aeb07201ad736b07a37fed8ea1b9..f0731c873267ed7efc05ea0a6aa1ff80f952b5b5 100644 --- a/src/mol-canvas3d/camera.ts +++ b/src/mol-canvas3d/camera.ts @@ -11,12 +11,9 @@ 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 } -// TODO: slab controls that modify near/far planes? - class Camera implements Object3D { readonly updatedViewProjection = new BehaviorSubject<Camera>(this); @@ -58,6 +55,8 @@ class Camera implements Object3D { const height = 2 * Math.tan(snapshot.fov / 2) * Vec3.distance(snapshot.position, snapshot.target); snapshot.zoom = this.viewport.height / height; + cameraSetClipping(snapshot, this.canvasProps); + switch (this.state.mode) { case 'orthographic': updateOrtho(this); break; case 'perspective': updatePers(this); break; @@ -102,13 +101,12 @@ class Camera implements Object3D { Vec3.setMagnitude(this.deltaDirection, this.state.direction, targetDistance) Vec3.sub(this.newPosition, target, this.deltaDirection) - 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) + const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state) + state.target = Vec3.clone(target) + state.radius = radius + state.position = Vec3.clone(this.newPosition) - return state; + return state } focus(target: Vec3, radius: number, durationMs?: number) { @@ -136,7 +134,7 @@ class Camera implements Object3D { this.updatedViewProjection.complete(); } - constructor(private scene: Scene, private canvasProps: Canvas3DProps, state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) { + constructor(private canvasProps: Canvas3DProps, state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) { this.viewport = viewport; Camera.copySnapshot(this.state, state); } @@ -186,6 +184,7 @@ namespace Camera { up: Vec3.create(0, 1, 0), target: Vec3.create(0, 0, 0), + radius: 10, near: 1, far: 10000, @@ -204,7 +203,9 @@ namespace Camera { // Normalized camera direction, from Target to Position, for some reason? direction: Vec3, up: Vec3, + target: Vec3, + radius: number near: number, far: number, @@ -223,7 +224,9 @@ namespace Camera { if (typeof source.position !== 'undefined') Vec3.copy(out.position, source.position); if (typeof source.direction !== 'undefined') Vec3.copy(out.direction, source.direction); if (typeof source.up !== 'undefined') Vec3.copy(out.up, source.up); + if (typeof source.target !== 'undefined') Vec3.copy(out.target, source.target); + if (typeof source.radius !== 'undefined') out.radius = source.radius; if (typeof source.near !== 'undefined') out.near = source.near; if (typeof source.far !== 'undefined') out.far = source.far; diff --git a/src/mol-canvas3d/camera/transition.ts b/src/mol-canvas3d/camera/transition.ts index a4639735bef760b6017886b93c3a775fabcb6a02..4a012dac31611663a21a1fa845d4d4d176d315fc 100644 --- a/src/mol-canvas3d/camera/transition.ts +++ b/src/mol-canvas3d/camera/transition.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> */ @@ -81,8 +81,9 @@ namespace CameraTransitionManager { Quat.slerp(rot, rot, Quat.rotationTo(Quat.zero(), source.up, target.up), t); Vec3.transformQuat(out.up, source.up, rot); - // Lerp target + // Lerp target & radius Vec3.lerp(out.target, source.target, target.target, t); + out.radius = lerp(source.radius, target.radius, t); // Update position const dist = -lerp(Vec3.distance(source.position, source.target), Vec3.distance(target.position, target.target), t); diff --git a/src/mol-canvas3d/camera/util.ts b/src/mol-canvas3d/camera/util.ts index 1d072ca231a9765131762602b554621450266016..29d5233105f94d56d45bd40c4c67631d977e4ef2 100644 --- a/src/mol-canvas3d/camera/util.ts +++ b/src/mol-canvas3d/camera/util.ts @@ -6,7 +6,6 @@ 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 } @@ -83,9 +82,9 @@ export function cameraLookAt(position: Vec3, up: Vec3, direction: Vec3, target: } } -export function cameraSetClipping(state: Camera.Snapshot, boundingSphere: Sphere3D, p: Canvas3DProps) { +export function cameraSetClipping(state: Camera.Snapshot, p: Canvas3DProps) { const cDist = Vec3.distance(state.position, state.target) - const bRadius = Math.max(10, boundingSphere.radius) + const bRadius = Math.max(1, state.radius) const nearFactor = (50 - p.clip[0]) / 50 const farFactor = -(50 - p.clip[1]) / 50 @@ -113,11 +112,6 @@ export function cameraSetClipping(state: Camera.Snapshot, boundingSphere: Sphere 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 diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 5cdee6e78c2654eb078ba432b5e0ebbaf9d89ef1..4f0b9d7eea665952a9d496bf1fbcee9ff3b1ff30 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/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, cameraSetClipping } from './camera/util' +import { Viewport } from './camera/util' import { createContext, WebGLContext, getGLContext } from '../mol-gl/webgl/context'; import { Representation } from '../mol-repr/representation'; import Scene from '../mol-gl/scene'; @@ -124,9 +124,7 @@ namespace Canvas3D { const scene = Scene.create(webgl) - const camera = new Camera(scene, p, { - near: 0.1, - far: 10000, + const camera = new Camera(p, { position: Vec3.create(0, 0, 100), mode: p.cameraMode }) @@ -174,18 +172,12 @@ namespace Canvas3D { } } - function setClipping() { - cameraSetClipping(camera.state, scene.boundingSphere, p); - } - function render(variant: 'pick' | 'draw', force: boolean) { if (scene.isCommiting) return false let didRender = false - controls.update(currentTime); - // TODO: is this a good fix? Also, setClipping does not work if the user has manually set a clipping plane. - if (!camera.transition.inTransition) setClipping(); - const cameraChanged = camera.updateMatrices(); + controls.update(currentTime) + const cameraChanged = camera.updateMatrices() multiSample.update(force || cameraChanged, currentTime) if (force || cameraChanged || multiSample.enabled) { diff --git a/src/mol-canvas3d/controls/bindings.ts b/src/mol-canvas3d/controls/bindings.ts new file mode 100644 index 0000000000000000000000000000000000000000..cbe1d5b99441a7fec7b63bf9d4094475b264a3ca --- /dev/null +++ b/src/mol-canvas3d/controls/bindings.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer'; + +const B = ButtonsType +const M = ModifiersKeys + +export interface Bindings { + drag: { + rotate: Bindings.Trigger + rotateZ: Bindings.Trigger + pan: Bindings.Trigger + zoom: Bindings.Trigger + // zoomFocus: Bindings.Trigger + }, + scroll: { + // focus: Bindings.Trigger + zoom: Bindings.Trigger + // zoomFocus: Bindings.Trigger + clipNear: Bindings.Trigger + } +} + +export namespace Bindings { + export type Trigger = { buttons: ButtonsType, modifiers?: ModifiersKeys } + + export function match(trigger: Trigger, buttons: ButtonsType, modifiers: ModifiersKeys) { + const { buttons: b, modifiers: m } = trigger + return ButtonsType.has(b, buttons) && + (!m || ModifiersKeys.areEqual(m, modifiers)) + } + + export const Default: Bindings = { + drag: { + rotate: { buttons: B.Flag.Primary, modifiers: M.create() }, + rotateZ: { buttons: B.Flag.Primary, modifiers: M.create({ shift: true }) }, + pan: { buttons: B.Flag.Secondary }, + zoom: { buttons: B.Flag.Auxilary } + }, + scroll: { + zoom: { buttons: B.Flag.Auxilary, modifiers: M.create() }, + clipNear: { buttons: B.Flag.Auxilary, modifiers: M.create({ shift: true }) } + } + } +} \ No newline at end of file diff --git a/src/mol-canvas3d/controls/trackball.ts b/src/mol-canvas3d/controls/trackball.ts index 192f044f4358c11a900990a7c49221af2332af75..49d3ff91eb7913e27db574e934f322f5e1b340b2 100644 --- a/src/mol-canvas3d/controls/trackball.ts +++ b/src/mol-canvas3d/controls/trackball.ts @@ -10,9 +10,11 @@ import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra'; import { cameraLookAt, Viewport } from '../camera/util'; -import InputObserver, { DragInput, WheelInput, ButtonsType, PinchInput } from '../../mol-util/input/input-observer'; -import { Object3D } from '../../mol-gl/object3d'; +import InputObserver, { DragInput, WheelInput, PinchInput } from '../../mol-util/input/input-observer'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { Camera } from '../camera'; +import { Bindings } from './bindings'; +import { absMax } from '../../mol-math/misc'; export const TrackballControlsParams = { noScroll: PD.Boolean(true, { isHidden: true }), @@ -28,7 +30,9 @@ export const TrackballControlsParams = { dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }), minDistance: PD.Numeric(0.01, {}, { isHidden: true }), - maxDistance: PD.Numeric(1e150, {}, { isHidden: true }) + maxDistance: PD.Numeric(1e150, {}, { isHidden: true }), + + bindings: PD.Value(Bindings.Default, { isHidden: true }) } export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams> @@ -44,11 +48,10 @@ interface TrackballControls { dispose: () => void } namespace TrackballControls { - export function create(input: InputObserver, object: Object3D & { target: Vec3 }, props: Partial<TrackballControlsProps> = {}): TrackballControls { + export function create(input: InputObserver, camera: Camera, props: Partial<TrackballControlsProps> = {}): TrackballControls { const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props } - const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 } - const target: Vec3 = object.target + const viewport = Viewport() let disposed = false @@ -60,91 +63,113 @@ namespace TrackballControls { let _isInteracting = false; // For internal use - const lastPosition = Vec3.zero() + const lastPosition = Vec3() - const _eye = Vec3.zero() + const _eye = Vec3() - const _movePrev = Vec2.zero() - const _moveCurr = Vec2.zero() + const _rotPrev = Vec2() + const _rotCurr = Vec2() + const _rotLastAxis = Vec3() + let _rotLastAngle = 0 - const _lastAxis = Vec3.zero() - let _lastAngle = 0 + const _zRotPrev = Vec2() + const _zRotCurr = Vec2() + let _zRotLastAngle = 0 - const _zoomStart = Vec2.zero() - const _zoomEnd = Vec2.zero() + const _zoomStart = Vec2() + const _zoomEnd = Vec2() - const _panStart = Vec2.zero() - const _panEnd = Vec2.zero() + const _panStart = Vec2() + const _panEnd = Vec2() // Initial values for reseting - const target0 = Vec3.clone(target) - const position0 = Vec3.clone(object.position) - const up0 = Vec3.clone(object.up) + const target0 = Vec3.clone(camera.target) + const position0 = Vec3.clone(camera.position) + const up0 = Vec3.clone(camera.up) - const mouseOnScreenVec2 = Vec2.zero() + const mouseOnScreenVec2 = Vec2() function getMouseOnScreen(pageX: number, pageY: number) { - Vec2.set( + return Vec2.set( mouseOnScreenVec2, (pageX - viewport.x) / viewport.width, (pageY - viewport.y) / viewport.height ); - return mouseOnScreenVec2; } - const mouseOnCircleVec2 = Vec2.zero() + const mouseOnCircleVec2 = Vec2() function getMouseOnCircle(pageX: number, pageY: number) { - Vec2.set( + return Vec2.set( mouseOnCircleVec2, - ((pageX - viewport.width * 0.5 - viewport.x) / (viewport.width * 0.5)), - ((viewport.height + 2 * (viewport.y - pageY)) / viewport.width) // screen.width intentional + (pageX - viewport.width * 0.5 - viewport.x) / (viewport.width * 0.5), + (viewport.height + 2 * (viewport.y - pageY)) / viewport.width // screen.width intentional ); - return mouseOnCircleVec2; } - const rotAxis = Vec3.zero() - const rotQuat = Quat.zero() - const rotEyeDir = Vec3.zero() - const rotObjUpDir = Vec3.zero() - const rotObjSideDir = Vec3.zero() - const rotMoveDir = Vec3.zero() + const rotAxis = Vec3() + const rotQuat = Quat() + const rotEyeDir = Vec3() + const rotObjUpDir = Vec3() + const rotObjSideDir = Vec3() + const rotMoveDir = Vec3() function rotateCamera() { - Vec3.set(rotMoveDir, _moveCurr[0] - _movePrev[0], _moveCurr[1] - _movePrev[1], 0); - let angle = Vec3.magnitude(rotMoveDir); + const dx = _rotCurr[0] - _rotPrev[0] + const dy = _rotCurr[1] - _rotPrev[1] + Vec3.set(rotMoveDir, dx, dy, 0); + + const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed; if (angle) { - Vec3.copy(_eye, object.position) - Vec3.sub(_eye, _eye, target) + Vec3.sub(_eye, camera.position, camera.target) - Vec3.normalize(rotEyeDir, Vec3.copy(rotEyeDir, _eye)) - Vec3.normalize(rotObjUpDir, Vec3.copy(rotObjUpDir, object.up)) + Vec3.normalize(rotEyeDir, _eye) + Vec3.normalize(rotObjUpDir, camera.up) Vec3.normalize(rotObjSideDir, Vec3.cross(rotObjSideDir, rotObjUpDir, rotEyeDir)) - Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, _moveCurr[1] - _movePrev[1]) - Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, _moveCurr[0] - _movePrev[0]) - - Vec3.add(rotMoveDir, Vec3.copy(rotMoveDir, rotObjUpDir), rotObjSideDir) + Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, dy) + Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, dx) + Vec3.add(rotMoveDir, rotObjUpDir, rotObjSideDir) Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye)) - - angle *= p.rotateSpeed; Quat.setAxisAngle(rotQuat, rotAxis, angle) Vec3.transformQuat(_eye, _eye, rotQuat) - Vec3.transformQuat(object.up, object.up, rotQuat) + Vec3.transformQuat(camera.up, camera.up, rotQuat) - Vec3.copy(_lastAxis, rotAxis) - _lastAngle = angle; - } else if (!p.staticMoving && _lastAngle) { - _lastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor); - Vec3.sub(_eye, Vec3.copy(_eye, object.position), target) - Quat.setAxisAngle(rotQuat, _lastAxis, _lastAngle) + Vec3.copy(_rotLastAxis, rotAxis) + _rotLastAngle = angle; + } else if (!p.staticMoving && _rotLastAngle) { + _rotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor); + Vec3.sub(_eye, camera.position, camera.target) + Quat.setAxisAngle(rotQuat, _rotLastAxis, _rotLastAngle) Vec3.transformQuat(_eye, _eye, rotQuat) - Vec3.transformQuat(object.up, object.up, rotQuat) + Vec3.transformQuat(camera.up, camera.up, rotQuat) + } + + Vec2.copy(_rotPrev, _rotCurr) + } + + const zRotQuat = Quat() + + function zRotateCamera() { + const dx = _zRotCurr[0] - _zRotPrev[0] + const dy = _zRotCurr[1] - _zRotPrev[1] + const angle = p.rotateSpeed * (-dx + dy) * -0.05 + + if (angle) { + Vec3.sub(_eye, camera.position, camera.target) + Quat.setAxisAngle(zRotQuat, _eye, angle) + Vec3.transformQuat(camera.up, camera.up, zRotQuat) + _zRotLastAngle = angle; + } else if (!p.staticMoving && _zRotLastAngle) { + _zRotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor); + Vec3.sub(_eye, camera.position, camera.target) + Quat.setAxisAngle(zRotQuat, _eye, _zRotLastAngle) + Vec3.transformQuat(camera.up, camera.up, zRotQuat) } - Vec2.copy(_movePrev, _moveCurr) + Vec2.copy(_zRotPrev, _zRotCurr) } function zoomCamera() { @@ -160,9 +185,9 @@ namespace TrackballControls { } } - const panMouseChange = Vec2.zero() - const panObjUp = Vec3.zero() - const panOffset = Vec3.zero() + const panMouseChange = Vec2() + const panObjUp = Vec3() + const panOffset = Vec3() function panCamera() { Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart) @@ -170,14 +195,14 @@ namespace TrackballControls { if (Vec2.squaredMagnitude(panMouseChange)) { Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * p.panSpeed) - Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), object.up) + Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), camera.up) Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0]) - Vec3.setMagnitude(panObjUp, object.up, panMouseChange[1]) + Vec3.setMagnitude(panObjUp, camera.up, panMouseChange[1]) Vec3.add(panOffset, panOffset, panObjUp) - Vec3.add(object.position, object.position, panOffset) - Vec3.add(target, target, panOffset) + Vec3.add(camera.position, camera.position, panOffset) + Vec3.add(camera.target, camera.target, panOffset) if (p.staticMoving) { Vec2.copy(_panStart, _panEnd) @@ -193,13 +218,13 @@ namespace TrackballControls { function checkDistances() { if (Vec3.squaredMagnitude(_eye) > p.maxDistance * p.maxDistance) { Vec3.setMagnitude(_eye, _eye, p.maxDistance) - Vec3.add(object.position, target, _eye) + Vec3.add(camera.position, camera.target, _eye) Vec2.copy(_zoomStart, _zoomEnd) } if (Vec3.squaredMagnitude(_eye) < p.minDistance * p.minDistance) { Vec3.setMagnitude(_eye, _eye, p.minDistance) - Vec3.add(object.position, target, _eye) + Vec3.add(camera.position, camera.target, _eye) Vec2.copy(_zoomStart, _zoomEnd) } } @@ -210,18 +235,19 @@ namespace TrackballControls { if (lastUpdated === t) return; if (p.spin) spin(t - lastUpdated); - Vec3.sub(_eye, object.position, target) + Vec3.sub(_eye, camera.position, camera.target) rotateCamera() + zRotateCamera() zoomCamera() panCamera() - Vec3.add(object.position, target, _eye) + Vec3.add(camera.position, camera.target, _eye) checkDistances() - cameraLookAt(object.position, object.up, object.direction, target) + cameraLookAt(camera.position, camera.up, camera.direction, camera.target) - if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON) { - Vec3.copy(lastPosition, object.position) + if (Vec3.squaredDistance(lastPosition, camera.position) > EPSILON) { + Vec3.copy(lastPosition, camera.position) } lastUpdated = t; @@ -229,53 +255,72 @@ namespace TrackballControls { /** Reset object's vectors and the target vector to their initial values */ function reset() { - Vec3.copy(target, target0) - Vec3.copy(object.position, position0) - Vec3.copy(object.up, up0) + Vec3.copy(camera.target, target0) + Vec3.copy(camera.position, position0) + Vec3.copy(camera.up, up0) - Vec3.sub(_eye, object.position, target) - cameraLookAt(object.position, object.up, object.direction, target) - Vec3.copy(lastPosition, object.position) + Vec3.sub(_eye, camera.position, camera.target) + cameraLookAt(camera.position, camera.up, camera.direction, camera.target) + Vec3.copy(lastPosition, camera.position) } // listeners - function onDrag({ pageX, pageY, buttons, isStart }: DragInput) { + function onDrag({ pageX, pageY, buttons, modifiers, isStart }: DragInput) { _isInteracting = true; + const dragRotate = Bindings.match(p.bindings.drag.rotate, buttons, modifiers) + const dragRotateZ = Bindings.match(p.bindings.drag.rotateZ, buttons, modifiers) + const dragPan = Bindings.match(p.bindings.drag.pan, buttons, modifiers) + const dragZoom = Bindings.match(p.bindings.drag.zoom, buttons, modifiers) + + getMouseOnCircle(pageX, pageY) + getMouseOnScreen(pageX, pageY) + if (isStart) { - if (buttons === ButtonsType.Flag.Primary) { - Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY)) - Vec2.copy(_movePrev, _moveCurr) - } else if (buttons === ButtonsType.Flag.Auxilary) { - Vec2.copy(_zoomStart, getMouseOnScreen(pageX, pageY)) + if (dragRotate) { + Vec2.copy(_rotCurr, mouseOnCircleVec2) + Vec2.copy(_rotPrev, _rotCurr) + } + if (dragRotateZ) { + Vec2.copy(_zRotCurr, mouseOnCircleVec2) + Vec2.copy(_zRotPrev, _zRotCurr) + } + if (dragZoom) { + Vec2.copy(_zoomStart, mouseOnScreenVec2) Vec2.copy(_zoomEnd, _zoomStart) - } else if (buttons === ButtonsType.Flag.Secondary) { - Vec2.copy(_panStart, getMouseOnScreen(pageX, pageY)) + } + if (dragPan) { + Vec2.copy(_panStart, mouseOnScreenVec2) Vec2.copy(_panEnd, _panStart) } } - if (buttons === ButtonsType.Flag.Primary) { - Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY)) - } else if (buttons === ButtonsType.Flag.Auxilary) { - Vec2.copy(_zoomEnd, getMouseOnScreen(pageX, pageY)) - } else if (buttons === ButtonsType.Flag.Secondary) { - Vec2.copy(_panEnd, getMouseOnScreen(pageX, pageY)) - } + if (dragRotate) Vec2.copy(_rotCurr, mouseOnCircleVec2) + if (dragRotateZ) Vec2.copy(_zRotCurr, mouseOnCircleVec2) + if (dragZoom) Vec2.copy(_zoomEnd, mouseOnScreenVec2) + if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2) } function onInteractionEnd() { _isInteracting = false; } - function onWheel({ dy }: WheelInput) { - _zoomEnd[1] += dy * 0.0001 + function onWheel({ dx, dy, dz, buttons, modifiers }: WheelInput) { + if (Bindings.match(p.bindings.scroll.zoom, buttons, modifiers)) { + _zoomEnd[1] += absMax(dx, dy, dz) * 0.0001 + } + if (Bindings.match(p.bindings.scroll.clipNear, buttons, modifiers)) { + const radius = Math.max(0, camera.state.radius + absMax(dx, dy, dz) * 0.005) + camera.setState({ radius }) + } } - function onPinch({ fraction }: PinchInput) { - _isInteracting = true; - _zoomEnd[1] += (fraction - 1) * 0.1 + function onPinch({ fraction, buttons, modifiers }: PinchInput) { + if (Bindings.match(p.bindings.scroll.zoom, buttons, modifiers)) { + _isInteracting = true; + _zoomEnd[1] += (fraction - 1) * 0.1 + } } function dispose() { @@ -292,7 +337,7 @@ namespace TrackballControls { function spin(deltaT: number) { const frameSpeed = (p.spinSpeed || 0) / 1000; _spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed; - if (!_isInteracting) Vec2.add(_moveCurr, _movePrev, _spinSpeed); + if (!_isInteracting) Vec2.add(_rotCurr, _rotPrev, _spinSpeed); } // force an update at start diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index f218a5463f000b92123437f8c9cf9c8b1fb17075..36c843ff1c673b20c5e4f14c7519d600ffcbb5fa 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -28,7 +28,7 @@ import { DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d'; function createRenderer(gl: WebGLRenderingContext) { const ctx = createContext(gl) - const camera = new Camera(Scene.create(ctx), DefaultCanvas3DParams, { + const camera = new Camera(DefaultCanvas3DParams, { near: 0.01, far: 10000, position: Vec3.create(0, 0, 50) diff --git a/src/mol-math/misc.ts b/src/mol-math/misc.ts index cfcb0d2355350d79adbd424a1b06947fa094caa9..0ccb844e2880edadde62b630911359f27486e63b 100644 --- a/src/mol-math/misc.ts +++ b/src/mol-math/misc.ts @@ -14,4 +14,19 @@ export function radToDeg (rad: number) { export function isPowerOfTwo (x: number) { return (x !== 0) && (x & (x - 1)) === 0 +} + +/** return the value that has the largest absolute value */ +export function absMax(...values: number[]) { + let max = 0 + let absMax = 0 + for (let i = 0, il = values.length; i < il; ++i) { + const value = values[i] + const abs = Math.abs(value) + if (abs > absMax) { + max = value + absMax = abs + } + } + return max } \ No newline at end of file diff --git a/src/mol-util/input/input-observer.ts b/src/mol-util/input/input-observer.ts index eb6f01e802d8e2203522680824deab52d4137f6e..077a1377103f8da8d9e62d4a0ddbb752a258efa6 100644 --- a/src/mol-util/input/input-observer.ts +++ b/src/mol-util/input/input-observer.ts @@ -60,11 +60,20 @@ export type ModifiersKeys = { meta: boolean } export namespace ModifiersKeys { - export const None: ModifiersKeys = { shift: false, alt: false, control: false, meta: false }; + export const None = create(); export function areEqual(a: ModifiersKeys, b: ModifiersKeys) { return a.shift === b.shift && a.alt === b.alt && a.control === b.control && a.meta === b.meta; } + + export function create(modifierKeys: Partial<ModifiersKeys> = {}): ModifiersKeys { + return { + shift: !!modifierKeys.shift, + alt: !!modifierKeys.alt, + control: !!modifierKeys.control, + meta: !!modifierKeys.meta + } + } } export type ButtonsType = BitFlags<ButtonsType.Flag> @@ -130,7 +139,7 @@ export type PinchInput = { fraction: number, distance: number, isStart: boolean -} +} & BaseInput export type ResizeInput = { @@ -342,12 +351,19 @@ namespace InputObserver { buttons = ButtonsType.Flag.Primary onPointerDown(ev.touches[0]) } else if (ev.touches.length >= 2) { - buttons = ButtonsType.Flag.Secondary + buttons = ButtonsType.Flag.Secondary & ButtonsType.Flag.Auxilary onPointerDown(getCenterTouch(ev)) const touchDistance = getTouchDistance(ev) lastTouchDistance = touchDistance - pinch.next({ distance: touchDistance, fraction: 1, delta: 0, isStart: true }) + pinch.next({ + distance: touchDistance, + fraction: 1, + delta: 0, + isStart: true, + buttons, + modifiers: getModifierKeys() + }) } } @@ -375,11 +391,14 @@ namespace InputObserver { buttons = ButtonsType.Flag.Secondary onPointerMove(getCenterTouch(ev)) } else { + buttons = ButtonsType.Flag.Auxilary pinch.next({ delta: touchDelta, fraction: lastTouchDistance / touchDistance, distance: touchDistance, - isStart: false + isStart: false, + buttons, + modifiers: getModifierKeys() }) } lastTouchDistance = touchDistance @@ -461,6 +480,8 @@ namespace InputObserver { const dy = (ev.deltaY || 0) * scale const dz = (ev.deltaZ || 0) * scale + buttons = ButtonsType.Flag.Auxilary + if (dx || dy || dz) { wheel.next({ dx, dy, dz, buttons, modifiers: getModifierKeys() }) }