UNPKG

molstar

Version:

A comprehensive macromolecular library.

334 lines 15.1 kB
"use strict"; /** * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Camera = void 0; var linear_algebra_1 = require("../mol-math/linear-algebra"); var util_1 = require("./camera/util"); var transition_1 = require("./camera/transition"); var rxjs_1 = require("rxjs"); var tmpPos1 = (0, linear_algebra_1.Vec3)(); var tmpPos2 = (0, linear_algebra_1.Vec3)(); var tmpClip = (0, linear_algebra_1.Vec4)(); var Camera = /** @class */ (function () { function Camera(state, viewport, props) { if (viewport === void 0) { viewport = util_1.Viewport.create(0, 0, 128, 128); } if (props === void 0) { props = {}; } this.view = linear_algebra_1.Mat4.identity(); this.projection = linear_algebra_1.Mat4.identity(); this.projectionView = linear_algebra_1.Mat4.identity(); this.inverseProjectionView = linear_algebra_1.Mat4.identity(); this.state = Camera.createDefaultSnapshot(); this.viewOffset = Camera.ViewOffset(); this.near = 1; this.far = 10000; this.fogNear = 5000; this.fogFar = 10000; this.zoom = 1; this.transition = new transition_1.CameraTransitionManager(this); this.stateChanged = new rxjs_1.BehaviorSubject(this.state); this.prevProjection = linear_algebra_1.Mat4.identity(); this.prevView = linear_algebra_1.Mat4.identity(); this.deltaDirection = (0, linear_algebra_1.Vec3)(); this.newPosition = (0, linear_algebra_1.Vec3)(); this.viewport = viewport; this.pixelScale = props.pixelScale || 1; Camera.copySnapshot(this.state, state); } Object.defineProperty(Camera.prototype, "pixelRatio", { get: function () { var dpr = (typeof window !== 'undefined') ? window.devicePixelRatio : 1; return dpr * this.pixelScale; }, enumerable: false, configurable: true }); Object.defineProperty(Camera.prototype, "position", { get: function () { return this.state.position; }, set: function (v) { linear_algebra_1.Vec3.copy(this.state.position, v); }, enumerable: false, configurable: true }); Object.defineProperty(Camera.prototype, "up", { get: function () { return this.state.up; }, set: function (v) { linear_algebra_1.Vec3.copy(this.state.up, v); }, enumerable: false, configurable: true }); Object.defineProperty(Camera.prototype, "target", { get: function () { return this.state.target; }, set: function (v) { linear_algebra_1.Vec3.copy(this.state.target, v); }, enumerable: false, configurable: true }); Camera.prototype.update = function () { var snapshot = this.state; if (snapshot.radiusMax === 0) { return false; } var height = 2 * Math.tan(snapshot.fov / 2) * linear_algebra_1.Vec3.distance(snapshot.position, snapshot.target); this.zoom = this.viewport.height / height; updateClip(this); switch (this.state.mode) { case 'orthographic': updateOrtho(this); break; case 'perspective': updatePers(this); break; default: throw new Error('unknown camera mode'); } var changed = !linear_algebra_1.Mat4.areEqual(this.projection, this.prevProjection, linear_algebra_1.EPSILON) || !linear_algebra_1.Mat4.areEqual(this.view, this.prevView, linear_algebra_1.EPSILON); if (changed) { linear_algebra_1.Mat4.mul(this.projectionView, this.projection, this.view); if (!linear_algebra_1.Mat4.tryInvert(this.inverseProjectionView, this.projectionView)) { linear_algebra_1.Mat4.copy(this.view, this.prevView); linear_algebra_1.Mat4.copy(this.projection, this.prevProjection); linear_algebra_1.Mat4.mul(this.projectionView, this.projection, this.view); return false; } linear_algebra_1.Mat4.copy(this.prevView, this.view); linear_algebra_1.Mat4.copy(this.prevProjection, this.projection); } return changed; }; Camera.prototype.setState = function (snapshot, durationMs) { this.transition.apply(snapshot, durationMs); this.stateChanged.next(snapshot); }; Camera.prototype.getSnapshot = function () { return Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state); }; Camera.prototype.getTargetDistance = function (radius) { return Camera.targetDistance(radius, this.state.fov, this.viewport.width, this.viewport.height); }; Camera.prototype.getFocus = function (target, radius, up, dir) { var r = Math.max(radius, 0.01); var targetDistance = this.getTargetDistance(r); linear_algebra_1.Vec3.sub(this.deltaDirection, this.target, this.position); if (dir) linear_algebra_1.Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection); linear_algebra_1.Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance); linear_algebra_1.Vec3.sub(this.newPosition, target, this.deltaDirection); var state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state); state.target = linear_algebra_1.Vec3.clone(target); state.radius = r; state.position = linear_algebra_1.Vec3.clone(this.newPosition); if (up) linear_algebra_1.Vec3.matchDirection(state.up, up, state.up); return state; }; Camera.prototype.getInvariantFocus = function (target, radius, up, dir) { var r = Math.max(radius, 0.01); var targetDistance = this.getTargetDistance(r); linear_algebra_1.Vec3.copy(this.deltaDirection, dir); linear_algebra_1.Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance); linear_algebra_1.Vec3.sub(this.newPosition, target, this.deltaDirection); var state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state); state.target = linear_algebra_1.Vec3.clone(target); state.radius = r; state.position = linear_algebra_1.Vec3.clone(this.newPosition); linear_algebra_1.Vec3.copy(state.up, up); return state; }; Camera.prototype.focus = function (target, radius, durationMs, up, dir) { if (radius > 0) { this.setState(this.getFocus(target, radius, up, dir), durationMs); } }; /** Transform point into 2D window coordinates. */ Camera.prototype.project = function (out, point) { return (0, util_1.cameraProject)(out, point, this.viewport, this.projectionView); }; /** * Transform point from screen space to 3D coordinates. * The point must have `x` and `y` set to 2D window coordinates * and `z` between 0 (near) and 1 (far); the optional `w` is not used. */ Camera.prototype.unproject = function (out, point) { return (0, util_1.cameraUnproject)(out, point, this.viewport, this.inverseProjectionView); }; /** World space pixel size at given `point` */ Camera.prototype.getPixelSize = function (point) { // project -> unproject of `point` does not exactly return the same // to get a sufficiently accurate measure we unproject the original // clip position in addition to the one shifted bey one pixel this.project(tmpClip, point); this.unproject(tmpPos1, tmpClip); tmpClip[0] += 1; this.unproject(tmpPos2, tmpClip); return linear_algebra_1.Vec3.distance(tmpPos1, tmpPos2); }; return Camera; }()); exports.Camera = Camera; (function (Camera) { function ViewOffset() { return { enabled: false, fullWidth: 1, fullHeight: 1, offsetX: 0, offsetY: 0, width: 1, height: 1 }; } Camera.ViewOffset = ViewOffset; function setViewOffset(out, fullWidth, fullHeight, offsetX, offsetY, width, height) { out.fullWidth = fullWidth; out.fullHeight = fullHeight; out.offsetX = offsetX; out.offsetY = offsetY; out.width = width; out.height = height; } Camera.setViewOffset = setViewOffset; function copyViewOffset(out, view) { out.enabled = view.enabled; out.fullWidth = view.fullWidth; out.fullHeight = view.fullHeight; out.offsetX = view.offsetX; out.offsetY = view.offsetY; out.width = view.width; out.height = view.height; } Camera.copyViewOffset = copyViewOffset; function targetDistance(radius, fov, width, height) { var r = Math.max(radius, 0.01); var aspect = width / height; var aspectFactor = (height < width ? 1 : aspect); return Math.abs((r / aspectFactor) / Math.sin(fov / 2)); } Camera.targetDistance = targetDistance; function createDefaultSnapshot() { return { mode: 'perspective', fov: Math.PI / 4, position: linear_algebra_1.Vec3.create(0, 0, 100), up: linear_algebra_1.Vec3.create(0, 1, 0), target: linear_algebra_1.Vec3.create(0, 0, 0), radius: 0, radiusMax: 10, fog: 50, clipFar: true }; } Camera.createDefaultSnapshot = createDefaultSnapshot; function copySnapshot(out, source) { if (!source) return out; if (typeof source.mode !== 'undefined') out.mode = source.mode; if (typeof source.fov !== 'undefined') out.fov = source.fov; if (typeof source.position !== 'undefined') linear_algebra_1.Vec3.copy(out.position, source.position); if (typeof source.up !== 'undefined') linear_algebra_1.Vec3.copy(out.up, source.up); if (typeof source.target !== 'undefined') linear_algebra_1.Vec3.copy(out.target, source.target); if (typeof source.radius !== 'undefined') out.radius = source.radius; if (typeof source.radiusMax !== 'undefined') out.radiusMax = source.radiusMax; if (typeof source.fog !== 'undefined') out.fog = source.fog; if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar; return out; } Camera.copySnapshot = copySnapshot; function areSnapshotsEqual(a, b) { return a.mode === b.mode && a.fov === b.fov && a.radius === b.radius && a.radiusMax === b.radiusMax && a.fog === b.fog && a.clipFar === b.clipFar && linear_algebra_1.Vec3.exactEquals(a.position, b.position) && linear_algebra_1.Vec3.exactEquals(a.up, b.up) && linear_algebra_1.Vec3.exactEquals(a.target, b.target); } Camera.areSnapshotsEqual = areSnapshotsEqual; })(Camera || (Camera = {})); exports.Camera = Camera; function updateOrtho(camera) { var viewport = camera.viewport, zoom = camera.zoom, near = camera.near, far = camera.far, viewOffset = camera.viewOffset; var fullLeft = -viewport.width / 2; var fullRight = viewport.width / 2; var fullTop = viewport.height / 2; var fullBottom = -viewport.height / 2; var dx = (fullRight - fullLeft) / (2 * zoom); var dy = (fullTop - fullBottom) / (2 * zoom); var cx = (fullRight + fullLeft) / 2; var cy = (fullTop + fullBottom) / 2; var left = cx - dx; var right = cx + dx; var top = cy + dy; var bottom = cy - dy; if (viewOffset.enabled) { var zoomW = zoom / (viewOffset.width / viewOffset.fullWidth); var zoomH = zoom / (viewOffset.height / viewOffset.fullHeight); var scaleW = (fullRight - fullLeft) / viewOffset.width; var scaleH = (fullTop - fullBottom) / viewOffset.height; left += scaleW * (viewOffset.offsetX / zoomW); right = left + scaleW * (viewOffset.width / zoomW); top -= scaleH * (viewOffset.offsetY / zoomH); bottom = top - scaleH * (viewOffset.height / zoomH); } // build projection matrix linear_algebra_1.Mat4.ortho(camera.projection, left, right, top, bottom, near, far); // build view matrix linear_algebra_1.Mat4.lookAt(camera.view, camera.position, camera.target, camera.up); } function updatePers(camera) { var aspect = camera.viewport.width / camera.viewport.height; var near = camera.near, far = camera.far, viewOffset = camera.viewOffset; var top = near * Math.tan(0.5 * camera.state.fov); var height = 2 * top; var width = aspect * height; var left = -0.5 * width; if (viewOffset.enabled) { left += viewOffset.offsetX * width / viewOffset.fullWidth; top -= viewOffset.offsetY * height / viewOffset.fullHeight; width *= viewOffset.width / viewOffset.fullWidth; height *= viewOffset.height / viewOffset.fullHeight; } // build projection matrix linear_algebra_1.Mat4.perspective(camera.projection, left, left + width, top, top - height, near, far); // build view matrix linear_algebra_1.Mat4.lookAt(camera.view, camera.position, camera.target, camera.up); } function updateClip(camera) { var _a = camera.state, radius = _a.radius, radiusMax = _a.radiusMax, mode = _a.mode, fog = _a.fog, clipFar = _a.clipFar; if (radius < 0.01) radius = 0.01; var normalizedFar = clipFar ? radius : radiusMax; var cameraDistance = linear_algebra_1.Vec3.distance(camera.position, camera.target); var near = cameraDistance - radius; var far = cameraDistance + normalizedFar; var fogNearFactor = -(50 - fog) / 50; var fogNear = cameraDistance - (normalizedFar * fogNearFactor); var fogFar = far; if (mode === 'perspective') { // set at least to 5 to avoid slow sphere impostor rendering near = Math.max(Math.min(radiusMax, 5), near); far = Math.max(5, far); } else { // not too close to 0 as it causes issues with outline rendering near = Math.max(Math.min(radiusMax, 5), near); far = Math.max(5, far); } if (near === far) { // make sure near and far are not identical to avoid Infinity in the projection matrix far = near + 0.01; } camera.near = near; camera.far = 2 * far; // avoid precision issues distingushing far objects from background camera.fogNear = fogNear; camera.fogFar = fogFar; } //# sourceMappingURL=camera.js.map