molstar
Version:
A comprehensive macromolecular library.
384 lines • 18 kB
JavaScript
/**
* Copyright (c) 2018-2021 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>
*
* This code has been modified from https://github.com/mrdoob/three.js/,
* copyright (c) 2010-2018 three.js authors. MIT License
*/
import { __assign } from "tslib";
import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra';
import { Viewport } from '../camera/util';
import { ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { absMax } from '../../mol-math/misc';
import { Binding } from '../../mol-util/binding';
var B = ButtonsType;
var M = ModifiersKeys;
var Trigger = Binding.Trigger;
export var DefaultTrackballBindings = {
dragRotate: Binding([Trigger(1 /* Primary */, M.create())], 'Rotate', 'Drag using ${triggers}'),
dragRotateZ: Binding([Trigger(1 /* Primary */, M.create({ shift: true }))], 'Rotate around z-axis', 'Drag using ${triggers}'),
dragPan: Binding([Trigger(2 /* Secondary */, M.create()), Trigger(1 /* Primary */, M.create({ control: true }))], 'Pan', 'Drag using ${triggers}'),
dragZoom: Binding.Empty,
dragFocus: Binding([Trigger(8 /* Forth */, M.create())], 'Focus', 'Drag using ${triggers}'),
dragFocusZoom: Binding([Trigger(4 /* Auxilary */, M.create())], 'Focus and zoom', 'Drag using ${triggers}'),
scrollZoom: Binding([Trigger(4 /* Auxilary */, M.create())], 'Zoom', 'Scroll using ${triggers}'),
scrollFocus: Binding([Trigger(4 /* Auxilary */, M.create({ shift: true }))], 'Clip', 'Scroll using ${triggers}'),
scrollFocusZoom: Binding.Empty,
};
export var TrackballControlsParams = {
noScroll: PD.Boolean(true, { isHidden: true }),
rotateSpeed: PD.Numeric(5.0, { min: 1, max: 10, step: 1 }),
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 }),
staticMoving: PD.Boolean(true, { isHidden: true }),
dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
gestureScaleFactor: PD.Numeric(1, {}, { isHidden: true }),
maxWheelDelta: PD.Numeric(0.02, {}, { isHidden: true }),
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true }),
/**
* minDistance = minDistanceFactor * boundingSphere.radius + minDistancePadding
* maxDistance = max(maxDistanceFactor * boundingSphere.radius, maxDistanceMin)
*/
autoAdjustMinMaxDistance: PD.MappedStatic('on', {
off: PD.EmptyGroup(),
on: PD.Group({
minDistanceFactor: PD.Numeric(0),
minDistancePadding: PD.Numeric(5),
maxDistanceFactor: PD.Numeric(10),
maxDistanceMin: PD.Numeric(20)
})
}, { isHidden: true })
};
export { TrackballControls };
var TrackballControls;
(function (TrackballControls) {
function create(input, camera, props) {
if (props === void 0) { props = {}; }
var p = __assign(__assign({}, PD.getDefaultValues(TrackballControlsParams)), props);
var viewport = Viewport.clone(camera.viewport);
var disposed = false;
var dragSub = input.drag.subscribe(onDrag);
var interactionEndSub = input.interactionEnd.subscribe(onInteractionEnd);
var wheelSub = input.wheel.subscribe(onWheel);
var pinchSub = input.pinch.subscribe(onPinch);
var gestureSub = input.gesture.subscribe(onGesture);
var _isInteracting = false;
// For internal use
var lastPosition = Vec3();
var _eye = Vec3();
var _rotPrev = Vec2();
var _rotCurr = Vec2();
var _rotLastAxis = Vec3();
var _rotLastAngle = 0;
var _zRotPrev = Vec2();
var _zRotCurr = Vec2();
var _zRotLastAngle = 0;
var _zoomStart = Vec2();
var _zoomEnd = Vec2();
var _focusStart = Vec2();
var _focusEnd = Vec2();
var _panStart = Vec2();
var _panEnd = Vec2();
// Initial values for reseting
var target0 = Vec3.clone(camera.target);
var position0 = Vec3.clone(camera.position);
var up0 = Vec3.clone(camera.up);
var mouseOnScreenVec2 = Vec2();
function getMouseOnScreen(pageX, pageY) {
return Vec2.set(mouseOnScreenVec2, (pageX - viewport.x) / viewport.width, (pageY - viewport.y) / viewport.height);
}
var mouseOnCircleVec2 = Vec2();
function getMouseOnCircle(pageX, pageY) {
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
);
}
var rotAxis = Vec3();
var rotQuat = Quat();
var rotEyeDir = Vec3();
var rotObjUpDir = Vec3();
var rotObjSideDir = Vec3();
var rotMoveDir = Vec3();
function rotateCamera() {
var dx = _rotCurr[0] - _rotPrev[0];
var dy = _rotCurr[1] - _rotPrev[1];
Vec3.set(rotMoveDir, dx, dy, 0);
var aspectRatio = input.width / input.height;
var angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio * aspectRatio;
if (angle) {
Vec3.sub(_eye, camera.position, camera.target);
Vec3.normalize(rotEyeDir, _eye);
Vec3.normalize(rotObjUpDir, camera.up);
Vec3.normalize(rotObjSideDir, Vec3.cross(rotObjSideDir, rotObjUpDir, rotEyeDir));
Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, dy);
Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, dx);
Vec3.add(rotMoveDir, rotObjUpDir, rotObjSideDir);
Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye));
Quat.setAxisAngle(rotQuat, rotAxis, angle);
Vec3.transformQuat(_eye, _eye, rotQuat);
Vec3.transformQuat(camera.up, camera.up, rotQuat);
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(camera.up, camera.up, rotQuat);
}
Vec2.copy(_rotPrev, _rotCurr);
}
var zRotQuat = Quat();
function zRotateCamera() {
var dx = _zRotCurr[0] - _zRotPrev[0];
var dy = _zRotCurr[1] - _zRotPrev[1];
var 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(_zRotPrev, _zRotCurr);
}
function zoomCamera() {
var factor = 1.0 + (_zoomEnd[1] - _zoomStart[1]) * p.zoomSpeed;
if (factor !== 1.0 && factor > 0.0) {
Vec3.scale(_eye, _eye, factor);
}
if (p.staticMoving) {
Vec2.copy(_zoomStart, _zoomEnd);
}
else {
_zoomStart[1] += (_zoomEnd[1] - _zoomStart[1]) * p.dynamicDampingFactor;
}
}
function focusCamera() {
var factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed;
if (factor !== 0.0) {
var radius = Math.max(1, camera.state.radius + camera.state.radius * factor);
camera.setState({ radius: radius });
}
if (p.staticMoving) {
Vec2.copy(_focusStart, _focusEnd);
}
else {
_focusStart[1] += (_focusEnd[1] - _focusStart[1]) * p.dynamicDampingFactor;
}
}
var panMouseChange = Vec2();
var panObjUp = Vec3();
var panOffset = Vec3();
function panCamera() {
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart);
if (Vec2.squaredMagnitude(panMouseChange)) {
var factor = input.pixelRatio * p.panSpeed;
panMouseChange[0] *= (1 / camera.zoom) * camera.viewport.width * factor;
panMouseChange[1] *= (1 / camera.zoom) * camera.viewport.height * factor;
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), camera.up);
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0]);
Vec3.setMagnitude(panObjUp, camera.up, panMouseChange[1]);
Vec3.add(panOffset, panOffset, panObjUp);
Vec3.add(camera.position, camera.position, panOffset);
Vec3.add(camera.target, camera.target, panOffset);
if (p.staticMoving) {
Vec2.copy(_panStart, _panEnd);
}
else {
Vec2.sub(panMouseChange, _panEnd, _panStart);
Vec2.scale(panMouseChange, panMouseChange, p.dynamicDampingFactor);
Vec2.add(_panStart, _panStart, panMouseChange);
}
}
}
/**
* Ensure the distance between object and target is within the min/max distance
* and not too large compared to `camera.state.radiusMax`
*/
function checkDistances() {
var maxDistance = Math.min(Math.max(camera.state.radiusMax * 1000, 0.01), p.maxDistance);
if (Vec3.squaredMagnitude(_eye) > maxDistance * maxDistance) {
Vec3.setMagnitude(_eye, _eye, maxDistance);
Vec3.add(camera.position, camera.target, _eye);
Vec2.copy(_zoomStart, _zoomEnd);
Vec2.copy(_focusStart, _focusEnd);
}
if (Vec3.squaredMagnitude(_eye) < p.minDistance * p.minDistance) {
Vec3.setMagnitude(_eye, _eye, p.minDistance);
Vec3.add(camera.position, camera.target, _eye);
Vec2.copy(_zoomStart, _zoomEnd);
Vec2.copy(_focusStart, _focusEnd);
}
}
function outsideViewport(x, y) {
x *= input.pixelRatio;
y *= input.pixelRatio;
return (x > viewport.x + viewport.width ||
input.height - y > viewport.y + viewport.height ||
x < viewport.x ||
input.height - y < viewport.y);
}
var lastUpdated = -1;
/** Update the object's position, direction and up vectors */
function update(t) {
if (lastUpdated === t)
return;
if (p.spin && lastUpdated > 0)
spin(t - lastUpdated);
Vec3.sub(_eye, camera.position, camera.target);
rotateCamera();
zRotateCamera();
zoomCamera();
focusCamera();
panCamera();
Vec3.add(camera.position, camera.target, _eye);
checkDistances();
if (Vec3.squaredDistance(lastPosition, camera.position) > EPSILON) {
Vec3.copy(lastPosition, camera.position);
}
lastUpdated = t;
}
/** Reset object's vectors and the target vector to their initial values */
function reset() {
Vec3.copy(camera.target, target0);
Vec3.copy(camera.position, position0);
Vec3.copy(camera.up, up0);
Vec3.sub(_eye, camera.position, camera.target);
Vec3.copy(lastPosition, camera.position);
}
// listeners
function onDrag(_a) {
var x = _a.x, y = _a.y, pageX = _a.pageX, pageY = _a.pageY, buttons = _a.buttons, modifiers = _a.modifiers, isStart = _a.isStart;
var isOutside = outsideViewport(x, y);
if (isStart && isOutside)
return;
if (!isStart && !_isInteracting)
return;
_isInteracting = true;
var dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers);
var dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers);
var dragPan = Binding.match(p.bindings.dragPan, buttons, modifiers);
var dragZoom = Binding.match(p.bindings.dragZoom, buttons, modifiers);
var dragFocus = Binding.match(p.bindings.dragFocus, buttons, modifiers);
var dragFocusZoom = Binding.match(p.bindings.dragFocusZoom, buttons, modifiers);
getMouseOnCircle(pageX, pageY);
getMouseOnScreen(pageX, pageY);
if (isStart) {
if (dragRotate) {
Vec2.copy(_rotCurr, mouseOnCircleVec2);
Vec2.copy(_rotPrev, _rotCurr);
}
if (dragRotateZ) {
Vec2.copy(_zRotCurr, mouseOnCircleVec2);
Vec2.copy(_zRotPrev, _zRotCurr);
}
if (dragZoom || dragFocusZoom) {
Vec2.copy(_zoomStart, mouseOnScreenVec2);
Vec2.copy(_zoomEnd, _zoomStart);
}
if (dragFocus) {
Vec2.copy(_focusStart, mouseOnScreenVec2);
Vec2.copy(_focusEnd, _focusStart);
}
if (dragPan) {
Vec2.copy(_panStart, mouseOnScreenVec2);
Vec2.copy(_panEnd, _panStart);
}
}
if (dragRotate)
Vec2.copy(_rotCurr, mouseOnCircleVec2);
if (dragRotateZ)
Vec2.copy(_zRotCurr, mouseOnCircleVec2);
if (dragZoom || dragFocusZoom)
Vec2.copy(_zoomEnd, mouseOnScreenVec2);
if (dragFocus)
Vec2.copy(_focusEnd, mouseOnScreenVec2);
if (dragFocusZoom) {
var dist = Vec3.distance(camera.state.position, camera.state.target);
camera.setState({ radius: dist / 5 });
}
if (dragPan)
Vec2.copy(_panEnd, mouseOnScreenVec2);
}
function onInteractionEnd() {
_isInteracting = false;
}
function onWheel(_a) {
var x = _a.x, y = _a.y, spinX = _a.spinX, spinY = _a.spinY, dz = _a.dz, buttons = _a.buttons, modifiers = _a.modifiers;
if (outsideViewport(x, y))
return;
var delta = absMax(spinX * 0.075, spinY * 0.075, dz * 0.0001);
if (delta < -p.maxWheelDelta)
delta = -p.maxWheelDelta;
else if (delta > p.maxWheelDelta)
delta = p.maxWheelDelta;
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_zoomEnd[1] += delta;
}
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
_focusEnd[1] += delta;
}
}
function onPinch(_a) {
var fractionDelta = _a.fractionDelta, buttons = _a.buttons, modifiers = _a.modifiers;
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_isInteracting = true;
_zoomEnd[1] += p.gestureScaleFactor * fractionDelta;
}
}
function onGesture(_a) {
var deltaScale = _a.deltaScale;
_isInteracting = true;
_zoomEnd[1] += p.gestureScaleFactor * deltaScale;
}
function dispose() {
if (disposed)
return;
disposed = true;
dragSub.unsubscribe();
wheelSub.unsubscribe();
pinchSub.unsubscribe();
gestureSub.unsubscribe();
interactionEndSub.unsubscribe();
}
var _spinSpeed = Vec2.create(0.005, 0);
function spin(deltaT) {
if (p.spinSpeed === 0)
return;
var frameSpeed = (p.spinSpeed || 0) / 1000;
_spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
if (!_isInteracting)
Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
}
function start(t) {
lastUpdated = -1;
update(t);
}
return {
viewport: viewport,
get props() { return p; },
setProps: function (props) {
Object.assign(p, props);
},
start: start,
update: update,
reset: reset,
dispose: dispose
};
}
TrackballControls.create = create;
})(TrackballControls || (TrackballControls = {}));
//# sourceMappingURL=trackball.js.map