From c7b618c246b9b76081109ad9a49dd690bd03de44 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Sun, 21 Jul 2019 18:13:10 -0700 Subject: [PATCH] lighting demo --- src/apps/demos/lighting/index.html | 87 +++++++++++++++ src/apps/demos/lighting/index.ts | 166 +++++++++++++++++++++++++++++ webpack.config.js | 1 + 3 files changed, 254 insertions(+) create mode 100644 src/apps/demos/lighting/index.html create mode 100644 src/apps/demos/lighting/index.ts diff --git a/src/apps/demos/lighting/index.html b/src/apps/demos/lighting/index.html new file mode 100644 index 000000000..97439a81d --- /dev/null +++ b/src/apps/demos/lighting/index.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> + <title>Mol* Lighting Demo</title> + <style> + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + #app { + position: absolute; + left: 160px; + top: 100px; + width: 600px; + height: 600px; + border: 1px solid #ccc; + } + + #controls { + position: absolute; + width: 150px; + top: 100px; + left: 780px; + } + + #controls > button { + display: block; + width: 100%; + text-align: left; + margin: 5px 0px; + } + + #controls > input, #controls > select { + width: 100%; + display: block; + } + </style> + <link rel="stylesheet" type="text/css" href="app.css" /> + <script type="text/javascript" src="./index.js"></script> + </head> + <body> + <div id='controls'></div> + <div id="app"></div> + <script> + LightingDemo.init('app') + LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' }) + + addHeader('Example PDB IDs'); + addControl('1M07', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })); + addControl('6HY0', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6HY0.cif', assemblyId: '1' })); + addControl('6QVK', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6QVK.cif', assemblyId: '1' })); + addControl('1RB8', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1RB8.cif', assemblyId: '1' })); + + addSeparator() + + addHeader('Lighting Presets'); + addControl('Illustrative', () => LightingDemo.setPreset('illustrative')); + addControl('Standard', () => LightingDemo.setPreset('standard')); + addControl('Ambient Occlusion', () => LightingDemo.setPreset('occlusion')); + + //////////////////////////////////////////////////////// + + function $(id) { return document.getElementById(id); } + + function addControl(label, action) { + var btn = document.createElement('button'); + btn.onclick = action; + btn.innerText = label; + $('controls').appendChild(btn); + } + + function addSeparator() { + var hr = document.createElement('br'); + $('controls').appendChild(hr); + } + + function addHeader(header) { + var h = document.createElement('h3'); + h.innerText = header; + $('controls').appendChild(h); + } + </script> + </body> +</html> \ No newline at end of file diff --git a/src/apps/demos/lighting/index.ts b/src/apps/demos/lighting/index.ts new file mode 100644 index 000000000..37fa7c974 --- /dev/null +++ b/src/apps/demos/lighting/index.ts @@ -0,0 +1,166 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { createPlugin, DefaultPluginSpec } from '../../../mol-plugin'; +import './index.html' +import { PluginContext } from '../../../mol-plugin/context'; +import { PluginCommands } from '../../../mol-plugin/command'; +import { StateTransforms } from '../../../mol-plugin/state/transforms'; +import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation'; +import { PluginStateObject as PSO } from '../../../mol-plugin/state/objects'; +import { StateBuilder } from '../../../mol-state'; +import { Canvas3DProps } from '../../../mol-canvas3d/canvas3d'; +require('mol-plugin/skin/light.scss') + +type SupportedFormats = 'cif' | 'pdb' +type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string } + +const Canvas3DPresets = { + illustrative: { + multiSample: { + mode: 'temporal' as Canvas3DProps['multiSample']['mode'] + }, + postprocessing: { + occlusionEnable: true, + occlusionBias: 0.8, + occlusionKernelSize: 6, + outlineEnable: true, + }, + renderer: { + ambientIntensity: 1, + lightIntensity: 0, + } + }, + occlusion: { + multiSample: { + mode: 'temporal' as Canvas3DProps['multiSample']['mode'] + }, + postprocessing: { + occlusionEnable: true, + occlusionBias: 0.8, + occlusionKernelSize: 6, + outlineEnable: false, + }, + renderer: { + ambientIntensity: 0.4, + lightIntensity: 0.6, + } + }, + standard: { + multiSample: { + mode: 'off' as Canvas3DProps['multiSample']['mode'] + }, + postprocessing: { + occlusionEnable: false, + outlineEnable: false, + }, + renderer: { + ambientIntensity: 0.4, + lightIntensity: 0.6, + } + } +} + +type Canvas3DPreset = keyof typeof Canvas3DPresets + +function getPreset(preset: Canvas3DPreset) { + switch (preset) { + case 'illustrative': return Canvas3DPresets['illustrative'] + case 'standard': return Canvas3DPresets['standard'] + case 'occlusion': return Canvas3DPresets['occlusion'] + } +} + +class LightingDemo { + plugin: PluginContext; + + init(target: string | HTMLElement) { + this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, { + ...DefaultPluginSpec, + layout: { + initial: { + isExpanded: false, + showControls: false + }, + controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' } + } + }); + + this.setPreset('illustrative'); + } + + setPreset(preset: Canvas3DPreset) { + const props = getPreset(preset) + PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { + ...props, + multiSample: { + ...this.plugin.canvas3d.props.multiSample, + ...props.multiSample + }, + renderer: { + ...this.plugin.canvas3d.props.renderer, + ...props.renderer + }, + postprocessing: { + ...this.plugin.canvas3d.props.postprocessing, + ...props.postprocessing + }, + }}); + } + + private download(b: StateBuilder.To<PSO.Root>, url: string) { + return b.apply(StateTransforms.Data.Download, { url, isBinary: false }) + } + + private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) { + const parsed = format === 'cif' + ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif) + : b.apply(StateTransforms.Model.TrajectoryFromPDB); + + return parsed + .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }) + .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' }); + } + + private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) { + visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill', {}, 'illustrative'), { ref: 'seq-visual' }); + visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' }); + return visualRoot; + } + + private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' }; + async load({ url, format = 'cif', assemblyId = '' }: LoadParams) { + let loadType: 'full' | 'update' = 'full'; + + const state = this.plugin.state.dataState; + + if (this.loadedParams.url !== url || this.loadedParams.format !== format) { + loadType = 'full'; + } else if (this.loadedParams.url === url) { + if (state.select('asm').length > 0) loadType = 'update'; + } + + let tree: StateBuilder.Root; + if (loadType === 'full') { + await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref }); + tree = state.build(); + this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId)); + } else { + tree = state.build(); + tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' })); + } + + await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree }); + this.loadedParams = { url, format, assemblyId }; + PluginCommands.Camera.Reset.dispatch(this.plugin, { }); + } +} + +(window as any).LightingDemo = new LightingDemo(); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 152fa40a4..0cc3d3c27 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -93,6 +93,7 @@ module.exports = [ createApp('viewer'), createApp('basic-wrapper'), createEntry('examples/proteopedia-wrapper/index', 'examples/proteopedia-wrapper', 'index'), + createEntry('apps/demos/lighting/index', 'demos/lighting', 'index'), createNodeApp('state-docs'), createNodeEntryPoint('preprocess', 'servers/model', 'model-server'), createApp('model-server-query'), -- GitLab