diff --git a/src/examples/alpha-orbitals/controls.tsx b/src/examples/alpha-orbitals/controls.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2fcd02874b7d44d533d0773ad7678af81d6c81fd --- /dev/null +++ b/src/examples/alpha-orbitals/controls.tsx @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import * as ReactDOM from 'react-dom'; +import { AlphaOrbitalsExample } from '.'; +import { ParameterControls } from '../../mol-plugin-ui/controls/parameters'; +import { PluginContextContainer } from '../../mol-plugin-ui/plugin'; + +export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) { + ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}> + <Controls orbitals={orbitals} /> + </PluginContextContainer>, parent); +} + +function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) { + const params = useBehavior(orbitals.params); + const values = useBehavior(orbitals.state); + + return <ParameterControls params={params as any} values={values} onChangeValues={(vs: any) => orbitals.state.next(vs)} />; +} + + +interface Behavior<T> { + value: T; + subscribe(f: (v: T) => void): { unsubscribe(): void }; +} + +export function useBehavior<T>(s: Behavior<T>): T; +// eslint-disable-next-line +export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined; +// eslint-disable-next-line +export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined { + const [value, setValue] = useState(s?.value); + + useEffect(() => { + if (!s) return; + let fst = true; + const sub = s.subscribe((v) => { + if (fst) { + fst = false; + if (v !== value) setValue(v); + } else setValue(v); + }); + + return () => { + sub.unsubscribe(); + }; + // eslint-disable-next-line + }, [s]); + + return value; +} \ No newline at end of file diff --git a/src/examples/alpha-orbitals/index.html b/src/examples/alpha-orbitals/index.html index 8c72b0deea5039aa37b5db38149cfad54f5010b4..01b04e78f3f55b008726034753a20a2076467705 100644 --- a/src/examples/alpha-orbitals/index.html +++ b/src/examples/alpha-orbitals/index.html @@ -12,19 +12,24 @@ } #app { position: absolute; - left: 160px; - top: 100px; - width: 800px; - height: 800px; - border: 1px solid #ccc; + left: 0; + top: 0; + bottom: 0; + right: 0; } + #controls { + position: absolute; + left: 8px; + top: 8px; + width: 300px; + } </style> <link rel="stylesheet" type="text/css" href="molstar.css" /> <script type="text/javascript" src="./index.js"></script> </head> <body> - <div id='controls'></div> <div id="app"></div> + <div id='controls'></div> <script> AlphaOrbitalsExample.init('app') </script> diff --git a/src/examples/alpha-orbitals/index.ts b/src/examples/alpha-orbitals/index.ts index c8102d828f610dd9ec18a133cc2e14ad922705e2..d10e5234fb839560ea76aabea49625274f6ab223 100644 --- a/src/examples/alpha-orbitals/index.ts +++ b/src/examples/alpha-orbitals/index.ts @@ -1,3 +1,5 @@ +import { BehaviorSubject } from 'rxjs'; +import { debounceTime, skip } from 'rxjs/operators'; /** * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * @@ -8,9 +10,15 @@ import { Basis, computeIsocontourValues } from '../../extensions/alpha-orbitals/ import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/orbitals'; import { createPluginAsync, DefaultPluginSpec } from '../../mol-plugin'; import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params'; +import { PluginStateObject } from '../../mol-plugin-state/objects'; import { StateTransforms } from '../../mol-plugin-state/transforms'; +import { PluginConfig } from '../../mol-plugin/config'; import { PluginContext } from '../../mol-plugin/context'; +import { StateObjectSelector } from '../../mol-state'; +import { Color } from '../../mol-util/color'; import { ColorNames } from '../../mol-util/color/names'; +import { ParamDefinition } from '../../mol-util/param-definition'; +import { mountControls } from './controls'; import { DemoMoleculeSDF, DemoOrbitals } from './example-data'; import './index.html'; import { CreateOrbitalVolume, StaticBasisAndOrbitals } from './transforms'; @@ -26,7 +34,12 @@ interface DemoInput { }[] } -class AlphaOrbitalsExample { +interface Params { + orbitalIndex: number, + isoValue: number +} + +export class AlphaOrbitalsExample { plugin: PluginContext; async init(target: string | HTMLElement) { @@ -37,8 +50,14 @@ class AlphaOrbitalsExample { isExpanded: false, showControls: false }, - controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' } - } + controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }, + }, + config: [ + [PluginConfig.Viewport.ShowExpand, false], + [PluginConfig.Viewport.ShowControls, false], + [PluginConfig.Viewport.ShowSelectionMode, false], + [PluginConfig.Viewport.ShowAnimation, false], + ] }); this.plugin.managers.interactivity.setProps({ granularity: 'element' }); @@ -47,6 +66,53 @@ class AlphaOrbitalsExample { moleculeSdf: DemoMoleculeSDF, ...DemoOrbitals }); + + mountControls(this, document.getElementById('controls')!); + } + + readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({ } as any); + readonly state = new BehaviorSubject<Params>({ orbitalIndex: 32, isoValue: 1 }); + + private volume?: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalVolume>; + private positive?: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof StateTransforms.Representation.VolumeRepresentation3D>; + private negative?: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof StateTransforms.Representation.VolumeRepresentation3D>; + private isovalues: { negative?: number, positive?: number } = { negative: void 0, positive: void 0 } + private currentParams: Params = { ...this.state.value }; + + private async setIndex() { + if (!this.volume?.isOk) return; + const state = this.state.value; + await this.plugin.build().to(this.volume).update(CreateOrbitalVolume, () => ({ index: state.orbitalIndex })).commit(); + this.currentParams.orbitalIndex = this.state.value.orbitalIndex; + this.isovalues = computeIsocontourValues(this.volume.data!.grid.cells.data as any, 0.85); + await this.setIsovalue(); + } + + private setIsovalue() { + const { positive, negative } = this.isovalues; + this.currentParams.isoValue = this.state.value.isoValue; + const update = this.plugin.build(); + update.to(this.positive!).update(this.volumeParams(positive, ColorNames.blue)); + update.to(this.negative!).update(this.volumeParams(negative, ColorNames.red)); + return update.commit(); + } + + private volumeParams(value: number | undefined, color: Color) { + return createVolumeRepresentationParams(this.plugin, this.volume!.data!, { + // type: 'isosurface', + // typeParams: { isoValue: { kind: 'absolute', absoluteValue: positive } }, + // color: 'uniform', + // colorParams: { value: ColorNames.blue } + type: 'direct-volume', + typeParams: { + renderMode: { + name: 'isosurface', + params: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * this.state.value.isoValue }, singleLayer: false } + } + }, + color: 'uniform', + colorParams: { value: color } + }); } async load(input: DemoInput) { @@ -58,40 +124,44 @@ class AlphaOrbitalsExample { const structure = await this.plugin.builders.structure.createStructure(model); const all = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'all'); - if (all) await this.plugin.builders.structure.representation.addRepresentation(all, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: { } } } }); + if (all) await this.plugin.builders.structure.representation.addRepresentation(all, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }); - const volumeRef = await this.plugin.build().toRoot() + const state = this.state.value; + + this.volume = await this.plugin.build().toRoot() .apply(StaticBasisAndOrbitals, { basis: input.basis, order: input.order, orbitals: input.orbitals }) - .apply(CreateOrbitalVolume, { index: 32 }) + .apply(CreateOrbitalVolume, { index: state.orbitalIndex }) .commit(); - if (!volumeRef.isOk) return; + if (!this.volume.isOk) { + this.volume = void 0; + return; + } // TODO: the isovalues are being computed twice. Need to add more flexible support to Volume object // for controlling them - const { negative, positive } = computeIsocontourValues(volumeRef.data!.grid.cells.data as any, 0.85); + this.isovalues = computeIsocontourValues(this.volume.data!.grid.cells.data as any, 0.85); + const { positive, negative } = this.isovalues; - const repr = this.plugin.build().to(volumeRef); + const repr = this.plugin.build().to(this.volume); - if (positive !== void 0) { - repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volumeRef.data!, { - type: 'isosurface', - typeParams: { isoValue: { kind: 'absolute', absoluteValue: positive } }, - color: 'uniform', - colorParams: { value: ColorNames.blue } - })); - } - - if (negative !== void 0) { - repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volumeRef.data!, { - type: 'isosurface', - typeParams: { isoValue: { kind: 'absolute', absoluteValue: negative } }, - color: 'uniform', - colorParams: { value: ColorNames.red } - })); - } + this.positive = repr.apply(StateTransforms.Representation.VolumeRepresentation3D, this.volumeParams(positive, ColorNames.blue)).selector; + this.negative = repr.apply(StateTransforms.Representation.VolumeRepresentation3D, this.volumeParams(negative, ColorNames.red)).selector; await repr.commit(); + + this.params.next({ + orbitalIndex: ParamDefinition.Numeric(this.currentParams.orbitalIndex, { min: 0, max: input.orbitals.length - 1 }), + isoValue: ParamDefinition.Numeric(1, { min: 0.5, max: 3, step: 0.1 }) + }); + + this.state.pipe(skip(1), debounceTime(1000 / 30)).subscribe(async params => { + if (params.orbitalIndex !== this.currentParams.orbitalIndex) { + this.setIndex(); + } else if (params.isoValue !== this.currentParams.isoValue) { + this.setIsovalue(); + } + }); } } diff --git a/src/examples/alpha-orbitals/transforms.ts b/src/examples/alpha-orbitals/transforms.ts index a7709e0998d1f6e02200a08dfc1d94f734267693..2185352228f97d7b97ffdd1e73d4bd9347cb6777 100644 --- a/src/examples/alpha-orbitals/transforms.ts +++ b/src/examples/alpha-orbitals/transforms.ts @@ -51,7 +51,7 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({ return Task.create('Orbital Volume', async ctx => { const data = await createSphericalCollocationGrid({ basis: a.data.basis, - cutoffThreshold: 0.0075, + cutoffThreshold: 0.0015, alphaOrbitals: a.data.orbitals[params.index].alpha, sphericalOrder: a.data.order, boxExpand: 4.5, diff --git a/src/extensions/alpha-orbitals/cubes.ts b/src/extensions/alpha-orbitals/cubes.ts index dab2f7904e029a383760dbecd5e4553e3a6348b0..e6ada49eeb14cb7845365469aacf674bb6f3f936 100644 --- a/src/extensions/alpha-orbitals/cubes.ts +++ b/src/extensions/alpha-orbitals/cubes.ts @@ -75,7 +75,7 @@ export function createSphericalCollocationGrid( sphericalOrder: params.sphericalOrder }; - console.log(cParams); + // console.log(cParams); console.time('gpu'); const pass = new AlphaOrbitalsPass(webgl!, cParams); @@ -83,10 +83,10 @@ export function createSphericalCollocationGrid( console.timeEnd('gpu'); // TODO: remove the 2nd run - console.time('gpu'); - const pass0 = new AlphaOrbitalsPass(webgl!, cParams); - pass0.getData(); - console.timeEnd('gpu'); + // console.time('gpu'); + // const pass0 = new AlphaOrbitalsPass(webgl!, cParams); + // pass0.getData(); + // console.timeEnd('gpu'); // if (false && webgl) { // } else { @@ -95,7 +95,7 @@ export function createSphericalCollocationGrid( // console.timeEnd('cpu'); // // } - console.log(matrixGL); + // console.log(matrixGL); // console.log(matrix); // for (let i = 0; i < matrixGL.length; i++) { diff --git a/src/extensions/alpha-orbitals/gpu/pass.ts b/src/extensions/alpha-orbitals/gpu/pass.ts index 0e15e5f8bd3d549be62d338f9b5620f66b1a3a50..1491aecf6fccc47acd730ff222508c2fdcdc8ecc 100644 --- a/src/extensions/alpha-orbitals/gpu/pass.ts +++ b/src/extensions/alpha-orbitals/gpu/pass.ts @@ -108,7 +108,7 @@ function createTextureData({ function getPostprocessingRenderable(ctx: WebGLContext, params: CollocationParams): AlphaOrbitalsRenderable { const data = createTextureData(params); - console.log(data); + // console.log(data); const values: Values<typeof AlphaOrbitalsSchema> = { ...QuadValues,