Sfoglia il codice sorgente

alpha-orbitals: controls

David Sehnal 4 anni fa
parent
commit
871f9635e3

+ 57 - 0
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;
+}

+ 11 - 6
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>

+ 96 - 26
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();
+            }
+        });
     }
 }
 

+ 1 - 1
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,

+ 6 - 6
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++) {

+ 1 - 1
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,