UNPKG

speck-renderer

Version:

Browser-based WebGL molecule renderer with the goal of producing figures that are as attractive as they are practical.

411 lines (360 loc) 14.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _dat = require('dat.gui/build/dat.gui'); var _dat2 = _interopRequireDefault(_dat); var _renderer = require('./renderer'); var _renderer2 = _interopRequireDefault(_renderer); var _view = require('./view'); var _view2 = _interopRequireDefault(_view); var _system = require('./system'); var _system2 = _interopRequireDefault(_system); var _xyz = require('./xyz'); var _xyz2 = _interopRequireDefault(_xyz); var _datGui = require('./config/dat-gui'); var _datGui2 = _interopRequireDefault(_datGui); var _caffeine = require('./molecules/caffeine'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Speck = function () { function Speck(_ref) { var canvasContainerID = _ref.canvasContainerID, canvasID = _ref.canvasID, options = _ref.options; _classCallCheck(this, Speck); this.canvasContainer = document.getElementById(canvasContainerID); this.canvas = document.getElementById(canvasID); this.system = _system2.default.new(); this.view = _view2.default.new(); var defaultOptions = { zoomRatio: 1 }; this.options = _extends({}, defaultOptions, options); this.gui = new _dat2.default.GUI({ load: _datGui2.default }); this.renderer = new Speck.Renderer({ canvas: this.canvas, canvasContainer: this.canvasContainer, system: this.system, view: this.view, options: this.options }); this._init(); this.loadStructure(_caffeine.caffeine); this.renderer.init(); } _createClass(Speck, [{ key: 'setOptions', value: function setOptions(options) { var newOptions = _extends({}, this.options, options); this.options = newOptions; this.renderer.options = newOptions; } }, { key: 'getOptions', value: function getOptions() { return this.options; } }, { key: '_init', value: function _init() { var _this = this; var speck = this.renderer; this.gui.remember(speck); var controllers = []; var atomsFolder = this.gui.addFolder('Atoms'); controllers.push(atomsFolder.add(speck, 'atomRadius', 0, 100)); controllers.push(atomsFolder.add(speck, 'relativeAtomRadius', 0, 100)); controllers.push(atomsFolder.add(speck, 'atomShade', 0, 100)); var bondsFolder = this.gui.addFolder('Bonds'); controllers.push(bondsFolder.add(speck, 'displayBonds')); controllers.push(bondsFolder.add(speck, 'bondRadius', 0, 100)); controllers.push(bondsFolder.add(speck, 'bondThreshold').min(0)); controllers.push(bondsFolder.add(speck, 'bondShade', 0, 100)); var shadingFolder = this.gui.addFolder('Shading'); controllers.push(shadingFolder.add(speck, 'ambientOcclusion', 0, 100)); controllers.push(shadingFolder.add(speck, 'brightness', 0, 100)); controllers.push(shadingFolder.add(speck, 'AOResolution', [16, 64, 128, 256, 512, 1024, 2048])); controllers.push(shadingFolder.add(speck, 'samplesPerFrame', [0, 1, 2, 4, 8, 16, 32, 64, 128])); var depthOfFieldFolder = this.gui.addFolder('Depth of Field'); controllers.push(depthOfFieldFolder.add(speck, 'depthOfFieldStrength', 0, 100)); controllers.push(depthOfFieldFolder.add(speck, 'depthOfFieldPosition', 0, 100)); var detailFolder = this.gui.addFolder('Detail'); controllers.push(detailFolder.add(speck, 'outlineStrength', 0, 100)); controllers.push(detailFolder.add(speck, 'antialiasingPasses').min(1).step(1)); controllers.push(detailFolder.add(speck, 'resolution', [64, 128, 256, 512, 1024, 2048])); this.gui.add(speck, 'center'); this.gui.add(speck, 'downloadImage'); var _loop = function _loop(i, len) { var controller = controllers[i]; controller.onChange(function (value) { switch (controller.property) { case "atomRadius": _this.renderer.view.atomScale = value / 100; break; case "relativeAtomRadius": _this.renderer.view.relativeAtomScale = value / 100; break; case "atomShade": _this.renderer.view.atomShade = value / 100; break; case "displayBonds": _this.renderer.view.bonds = value; _this.renderer.renderer.setSystem(_this.renderer.system, _this.renderer.view); break; case "bondRadius": _this.renderer.view.bondScale = value / 100; break; case "bondThreshold": _this.renderer.view.bondThreshold = value; _this.renderer.renderer.setSystem(_this.renderer.system, _this.renderer.view); break; case "bondShade": _this.renderer.view.bondShade = value / 100; break; case "ambientOcclusion": _this.renderer.view.ao = value / 100; break; case "brightness": _this.renderer.view.brightness = value / 100; break; case "AOResolution": _this.renderer.renderer.setResolution(speck.resolution, value); break; case "samplesPerFrame": _this.renderer.view.spf = value; break; case "depthOfFieldStrength": _this.renderer.view.dofStrength = value / 100; break; case "depthOfFieldPosition": _this.renderer.view.dofPosition = value / 100; break; case "outlineStrength": _this.renderer.view.outline = value / 100; break; case "antialiasingPasses": _this.renderer.view.fxaa = value; break; case "resolution": _this.renderer.renderer.setResolution(value, speck.AOResolution); break; } _view2.default.resolve(_this.renderer.view); _this.renderer.reset(); }); }; for (var i = 0, len = controllers.length; i < len; i++) { _loop(i, len); } this.gui.closed = true; } }, { key: 'loadStructure', value: function loadStructure(molecule) { var structure = (0, _xyz2.default)(molecule)[0]; this.system = _system2.default.new(); for (var i = 0; i < structure.length; i++) { var a = structure[i]; var x = a.position[0]; var y = a.position[1]; var z = a.position[2]; _system2.default.addAtom(this.system, a.symbol, x, y, z); } _system2.default.center(this.system); _system2.default.calculateBonds(this.system); this.renderer.system = this.system; this.renderer.renderer.setSystem(this.system, this.view); _view2.default.center(this.view, this.system, this.options.zoomRatio); this.renderer.reset(); } }, { key: 'destroy', value: function destroy() { this.gui.destroy(); this.renderer.destroy(); } }]); return Speck; }(); Speck.Renderer = function () { function _class(_ref2) { var canvas = _ref2.canvas, canvasContainer = _ref2.canvasContainer, system = _ref2.system, view = _ref2.view, options = _ref2.options; _classCallCheck(this, _class); this.atomRadius = 60; this.relativeAtomRadius = 100; this.atomShade = 50; this.displayBonds = true; this.bondRadius = 50; this.bondThreshold = 1.2; this.bondShade = 50; this.ambientOcclusion = 75; this.brightness = 50; this.AOResolution = 512; this.samplesPerFrame = 32; this.depthOfFieldStrength = 0; this.depthOfFieldPosition = 50; this.outlineStrength = 0; this.antialiasingPasses = 1; this.resolution = 512; this.lastX = 0.0; this.lastY = 0.0; this.mouseDown = false; this.needReset = false; this.system = system; this.view = view; this.canvas = canvas; this.canvasContainer = canvasContainer; this.options = options; this.renderer = new _renderer2.default(this.canvas, this.view.resolution, this.view.aoRes); } _createClass(_class, [{ key: 'init', value: function init() { this._createListeners(); this._loop(); this.view.atomScale = this.atomRadius / 100; this.view.relativeAtomScale = this.relativeAtomRadius / 100; this.view.atomShade = this.atomShade / 100; this.view.bonds = this.displayBonds; this.view.bondScale = this.bondRadius / 100; this.view.bondThreshold = this.bondThreshold; this.view.bondShade = this.bondShade / 100; this.view.ao = this.ambientOcclusion / 100; this.view.brightness = this.brightness / 100; this.view.spf = this.samplesPerFrame; this.view.dofStrength = this.depthOfFieldStrength / 100; this.view.dofPosition = this.depthOfFieldPosition / 100; this.view.outline = this.outlineStrength / 100; this.view.fxaa = this.antialiasingPasses; this.renderer.setSystem(this.system, this.view); this.renderer.setResolution(this.resolution, this.AOResolution); _view2.default.resolve(this.view); this.reset(); } }, { key: 'reset', value: function reset() { this.needReset = true; } }, { key: 'center', value: function center() { _view2.default.center(this.view, this.system, this.options.zoomRatio); this.needReset = true; } }, { key: 'downloadImage', value: function downloadImage() { this.renderer.render(this.view); var imgURL = this.canvas.toDataURL("image/png"); var iframe = "<iframe width='100%' height='100%' style={border: none, overflow: auto} src='" + imgURL + "'></iframe>"; var x = window.open(); x.document.open(); x.document.write(iframe); x.document.body.style.margin = "0"; x.document.body.style.height = "100%"; x.document.body.style.width = "100%"; x.document.close(); } }, { key: 'destroy', value: function destroy() { this._destroyListeners(); this._endLoop(); } }, { key: '_canvasScrollListener', value: function _canvasScrollListener(event) { var wd = 0; if (event.deltaY < 0) { wd = 1; } else { wd = -1; } this.view.zoom = this.view.zoom * (wd === 1 ? 1 / 0.9 : 0.9); this.needReset = true; event.preventDefault(); } }, { key: '_canvasMousedownListener', value: function _canvasMousedownListener(event) { document.body.style.cursor = "none"; this.mouseDown = true; this.lastX = event.clientX; this.lastY = event.clientY; } }, { key: '_windowMouseupListener', value: function _windowMouseupListener(event) { document.body.style.cursor = "default"; this.mouseDown = false; } }, { key: '_windowMousemoveListener', value: function _windowMousemoveListener(event) { if (!this.mouseDown) { return; } var dx = event.clientX - this.lastX; var dy = event.clientY - this.lastY; if (dx == 0 && dy == 0) { return; } if (event.shiftKey) { _view2.default.translate(this.view, dx, dy); } else { _view2.default.rotate(this.view, dx, dy); } this.needReset = true; this.lastX = event.clientX; this.lastY = event.clientY; } }, { key: '_createListeners', value: function _createListeners() { this.canvasContainer.addEventListener("wheel", this._canvasScrollListener.bind(this)); this.canvasContainer.addEventListener("mousedown", this._canvasMousedownListener.bind(this)); window.addEventListener("mouseup", this._windowMouseupListener.bind(this)); window.addEventListener("mousemove", this._windowMousemoveListener.bind(this)); this.mouseCheckInterval = setInterval(function () { if (!this.mouseDown) { document.body.style.cursor = "default"; } }, 20); } }, { key: '_destroyListeners', value: function _destroyListeners() { this.canvasContainer.removeEventListener("wheel", this._canvasScrollListener.bind(this)); this.canvasContainer.removeEventListener("mousedown", this._canvasMousedownListener.bind(this)); window.removeEventListener("mouseup", this._windowMouseupListener.bind(this)); window.removeEventListener("mousemove", this._windowMousemoveListener.bind(this)); clearInterval(this.mouseCheckInterval); } }, { key: '_loop', value: function _loop() { // document.getElementById("ao-indicator").style.width = Math.min(100, (renderer.getAOProgress() * 100)) + "%"; if (this.needReset) { this.renderer.reset(); } this.needReset = false; this.renderer.render(this.view); this.reqAnimationFrame = requestAnimationFrame(this._loop.bind(this)); } }, { key: '_endLoop', value: function _endLoop() { cancelAnimationFrame(this.reqAnimationFrame); } }, { key: 'dataURL', get: function get() { this.renderer.render(this.view); return this.canvas.toDataURL("image/png"); } }]); return _class; }(); exports.default = Speck;