Prechádzať zdrojové kódy

wip, load volume, volume gui

Alexander Rose 6 rokov pred
rodič
commit
f9343a8032

+ 20 - 19
package-lock.json

@@ -242,7 +242,8 @@
     "@types/webgl2": {
       "version": "0.0.4",
       "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
-      "integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
+      "integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw==",
+      "dev": true
     },
     "@webassemblyjs/ast": {
       "version": "1.7.8",
@@ -475,7 +476,7 @@
     },
     "adjust-sourcemap-loader": {
       "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-1.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-1.2.0.tgz",
       "integrity": "sha512-958oaHHVEXMvsY7v7cC5gEkNIcoaAVIhZ4mBReYVZJOTP9IgKmzLjIOhTtzpLMu+qriXvLsVjJ155EeInp45IQ==",
       "dev": true,
       "requires": {
@@ -969,7 +970,7 @@
     },
     "babel-plugin-istanbul": {
       "version": "4.1.6",
-      "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz",
+      "resolved": "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz",
       "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==",
       "dev": true,
       "requires": {
@@ -987,7 +988,7 @@
     },
     "babel-plugin-syntax-object-rest-spread": {
       "version": "6.13.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
+      "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
       "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=",
       "dev": true
     },
@@ -1353,7 +1354,7 @@
     },
     "browserify-aes": {
       "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
       "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
       "dev": true,
       "requires": {
@@ -1398,7 +1399,7 @@
     },
     "browserify-rsa": {
       "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+      "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
       "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
       "dev": true,
       "requires": {
@@ -1450,7 +1451,7 @@
     },
     "buffer": {
       "version": "4.9.1",
-      "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+      "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
       "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
       "dev": true,
       "requires": {
@@ -2077,7 +2078,7 @@
     },
     "create-hash": {
       "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
       "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
       "dev": true,
       "requires": {
@@ -2090,7 +2091,7 @@
     },
     "create-hmac": {
       "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
       "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
       "dev": true,
       "requires": {
@@ -2465,7 +2466,7 @@
     },
     "diffie-hellman": {
       "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+      "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
       "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
       "dev": true,
       "requires": {
@@ -7192,7 +7193,7 @@
     },
     "parse-asn1": {
       "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
+      "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
       "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
       "dev": true,
       "requires": {
@@ -8158,7 +8159,7 @@
       "dependencies": {
         "convert-source-map": {
           "version": "0.3.5",
-          "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
+          "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
           "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=",
           "dev": true
         }
@@ -8535,7 +8536,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -8834,7 +8835,7 @@
     },
     "sha.js": {
       "version": "2.4.11",
-      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
       "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
       "dev": true,
       "requires": {
@@ -9401,7 +9402,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -10260,7 +10261,7 @@
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -11318,7 +11319,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -11360,7 +11361,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -11498,7 +11499,7 @@
     },
     "yargs": {
       "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
       "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==",
       "dev": true,
       "requires": {

+ 23 - 18
src/apps/canvas/app.ts

@@ -5,21 +5,22 @@
  */
 
 import Viewer from 'mol-view/viewer';
-import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl } from './util';
+import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl, getVolumeFromCcp4, getCcp4FromFile } from './util';
 import { StructureView } from './structure-view';
 import { BehaviorSubject } from 'rxjs';
 import { CifBlock } from 'mol-io/reader/cif';
-import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4';
-import { VolumeRepresentation } from 'mol-geo/representation/volume';
-import IsosurfaceVisual from 'mol-geo/representation/volume/isosurface';
+import { VolumeView } from './volume-view';
+import { Ccp4File } from 'mol-io/reader/ccp4/schema';
 
 export class App {
     viewer: Viewer
     container: HTMLDivElement | null = null;
     canvas: HTMLCanvasElement | null = null;
     structureView: StructureView | null = null;
+    volumeView: VolumeView | null = null;
 
-    pdbIdLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null)
+    structureLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null)
+    volumeLoaded: BehaviorSubject<VolumeView | null> = new BehaviorSubject<VolumeView | null>(null)
 
     initViewer(_canvas: HTMLCanvasElement, _container: HTMLDivElement) {
         this.canvas = _canvas
@@ -63,7 +64,7 @@ export class App {
     async loadMmcif(cif: CifBlock, assemblyId?: string) {
         const models = await this.runTask(getModelsFromMmcif(cif), 'Build models')
         this.structureView = await this.runTask(StructureView(this, this.viewer, models, { assemblyId }), 'Init structure view')
-        this.pdbIdLoaded.next(this.structureView)
+        this.structureLoaded.next(this.structureView)
     }
 
     async loadPdbIdOrMmcifUrl(idOrUrl: string, options?: { assemblyId?: string, binary?: boolean }) {
@@ -82,17 +83,21 @@ export class App {
 
     //
 
-    async loadCcp4File() {
-        const url = 'http://localhost:8091/ngl/data/betaGal.mrc'
-        const ccp4 = await getCcp4FromUrl(url)
-        console.log(ccp4)
-        const volume = await volumeFromCcp4(ccp4).run()
-        const volRepr = VolumeRepresentation(IsosurfaceVisual)
-        await volRepr.createOrUpdate({
-            isoValue: 1
-        }, volume).run()
-        this.viewer.add(volRepr)
-        console.log('volRepr', volRepr)
-        this.viewer.requestDraw(true)
+    async loadCcp4(ccp4: Ccp4File) {
+        const volume = await this.runTask(getVolumeFromCcp4(ccp4), 'Get Volume')
+        this.volumeView = await this.runTask(VolumeView(this, this.viewer, volume), 'Init volume view')
+        this.volumeLoaded.next(this.volumeView)
+    }
+
+    async loadCcp4File(file: File) {
+        if (this.volumeView) this.volumeView.destroy();
+        const ccp4 = await this.runTask(getCcp4FromFile(file), 'Load CCP4 from file')
+        this.loadCcp4(ccp4)
+    }
+
+    async loadCcp4Url(url: string) {
+        if (this.volumeView) this.volumeView.destroy();
+        const ccp4 = await this.runTask(getCcp4FromUrl(url), 'Load CCP4 from URL')
+        this.loadCcp4(ccp4)
     }
 }

+ 24 - 5
src/apps/canvas/component/app.tsx

@@ -10,6 +10,8 @@ import { App } from '../app';
 import { Viewport } from './viewport';
 import { StructureViewComponent } from './structure-view';
 import { Examples } from '../examples';
+import { VolumeViewComponent } from './volume-view';
+import { VolumeView } from '../volume-view';
 
 export interface AppProps {
     app: App
@@ -17,25 +19,28 @@ export interface AppProps {
 
 export interface AppState {
     structureView: StructureView | null,
+    volumeView: VolumeView | null,
     binary: boolean
 }
 
 export class AppComponent extends React.Component<AppProps, AppState> {
     state = {
         structureView: this.props.app.structureView,
+        volumeView: this.props.app.volumeView,
         binary: false
     }
 
     componentDidMount() {
-        this.props.app.pdbIdLoaded.subscribe((structureView) => {
-            this.setState({
-                structureView: this.props.app.structureView
-            })
+        this.props.app.structureLoaded.subscribe((structureView) => {
+            this.setState({ structureView: this.props.app.structureView })
+        })
+        this.props.app.volumeLoaded.subscribe((volumeView) => {
+            this.setState({ volumeView: this.props.app.volumeView })
         })
     }
 
     render() {
-        const { structureView } = this.state
+        const { structureView, volumeView } = this.state
 
         return <div style={{width: '100%', height: '100%'}}>
             <div style={{left: '0px', right: '350px', height: '100%', position: 'absolute'}}>
@@ -69,6 +74,16 @@ export class AppComponent extends React.Component<AppProps, AppState> {
                         }}
                     />
                 </div>
+                <div>
+                    <span>Load CCP4/MRC file </span>
+                    <input
+                        accept='*.ccp4,*.mrc, *.map'
+                        type='file'
+                        onChange={e => {
+                            if (e.target.files) this.props.app.loadCcp4File(e.target.files[0])
+                        }}
+                    />
+                </div>
                 <div>
                     <span>Load example </span>
                     <select
@@ -87,6 +102,10 @@ export class AppComponent extends React.Component<AppProps, AppState> {
                 <div style={{marginBottom: '10px'}}>
                     {structureView ? <StructureViewComponent structureView={structureView} /> : ''}
                 </div>
+                <hr/>
+                <div style={{marginBottom: '10px'}}>
+                    {volumeView ? <VolumeViewComponent volumeView={volumeView} /> : ''}
+                </div>
             </div>
         </div>;
     }

+ 0 - 11
src/apps/canvas/component/structure-view.tsx

@@ -10,17 +10,6 @@ import { StructureRepresentation } from 'mol-geo/representation/structure';
 import { RepresentationComponent } from './representation';
 import { Representation } from 'mol-geo/representation';
 
-// export function FileInput (props: {
-//     accept: string
-//     onChange: (v: FileList | null) => void,
-// }) {
-//     return <input
-//         accept={props.accept || '*.*'}
-//         type='file'
-//         onChange={e => props.onChange.call(null, e.target.files)}
-//     />
-// }
-
 export interface StructureViewComponentProps {
     structureView: StructureView
 }

+ 104 - 0
src/apps/canvas/component/volume-view.tsx

@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+import { RepresentationComponent } from './representation';
+import { Representation } from 'mol-geo/representation';
+import { VolumeView } from '../volume-view';
+import { VolumeRepresentation } from 'mol-geo/representation/volume';
+
+export interface VolumeViewComponentProps {
+    volumeView: VolumeView
+}
+
+export interface VolumeViewComponentState {
+    volumeView: VolumeView
+    label: string
+    active: { [k: string]: boolean }
+    volumeRepresentations: { [k: string]: VolumeRepresentation<any> }
+}
+
+export class VolumeViewComponent extends React.Component<VolumeViewComponentProps, VolumeViewComponentState> {
+    state = this.stateFromVolumeView(this.props.volumeView)
+
+    private stateFromVolumeView(vv: VolumeView) {
+        return {
+            volumeView: vv,
+            label: vv.label,
+            active: vv.active,
+            volumeRepresentations: vv.volumeRepresentations
+        }
+    }
+
+    componentWillMount() {
+        this.setState(this.stateFromVolumeView(this.props.volumeView))
+    }
+
+    componentDidMount() {
+        const vv = this.props.volumeView
+
+        this.props.volumeView.updated.subscribe(() => this.setState({
+            volumeRepresentations: vv.volumeRepresentations
+        }))
+    }
+
+    componentWillReceiveProps(nextProps: VolumeViewComponentProps) {
+        if (nextProps.volumeView !== this.props.volumeView) {
+            this.setState(this.stateFromVolumeView(nextProps.volumeView))
+
+            nextProps.volumeView.updated.subscribe(() => this.setState({
+                volumeRepresentations: nextProps.volumeView.volumeRepresentations
+            }))
+        }
+    }
+
+    // async update(state: Partial<VolumeViewComponentState>) {
+    //     const vv = this.state.volumeView
+    //     this.setState(this.stateFromVolumeView(vv))
+    // }
+
+    render() {
+        const { volumeView, label, active, volumeRepresentations } = this.state
+
+        return <div>
+            <div>
+                <h2>{label}</h2>
+            </div>
+            <div>
+                <div>
+                    <h4>Active</h4>
+                    { Object.keys(active).map((k, i) => {
+                        return <div key={i}>
+                            <input
+                                type='checkbox'
+                                checked={active[k]}
+                                onChange={(e) => {
+                                    volumeView.setVolumeRepresentation(k, e.target.checked)
+                                }}
+                            /> {k}
+                        </div>
+                    } ) }
+                </div>
+                <div>
+                    <h3>Volume Representations</h3>
+                    { Object.keys(volumeRepresentations).map((k, i) => {
+                        if (active[k]) {
+                            return <div key={i}>
+                                <RepresentationComponent
+                                    repr={volumeRepresentations[k] as Representation<any>}
+                                    viewer={volumeView.viewer}
+                                    app={volumeView.app}
+                                />
+                            </div>
+                        } else {
+                            return ''
+                        }
+                    } ) }
+                </div>
+            </div>
+        </div>;
+    }
+}

+ 11 - 1
src/apps/canvas/util.ts

@@ -4,11 +4,13 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { readUrl, readFile, readUrlAsBuffer } from 'mol-util/read';
+import { readUrl, readFile, readUrlAsBuffer, readFileAsBuffer } from 'mol-util/read';
 import CIF, { CifBlock } from 'mol-io/reader/cif'
 import { Model, Format, StructureSymmetry, Structure } from 'mol-model/structure';
 import CCP4 from 'mol-io/reader/ccp4/parser'
 import { FileHandle } from 'mol-io/common/file-handle';
+import { Ccp4File } from 'mol-io/reader/ccp4/schema';
+import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4';
 // import { parse as parseObj } from 'mol-io/reader/obj/parser'
 
 // export async function getObjFromUrl(url: string) {
@@ -53,9 +55,17 @@ export async function getCcp4FromUrl(url: string) {
     return getCcp4FromData(await readUrlAsBuffer(url))
 }
 
+export async function getCcp4FromFile(file: File) {
+    return getCcp4FromData(await readFileAsBuffer(file))
+}
+
 export async function getCcp4FromData(data: Uint8Array) {
     const file = FileHandle.fromBuffer(data)
     const parsed = await CCP4(file).run()
     if (parsed.isError) throw parsed
     return parsed.result
+}
+
+export async function getVolumeFromCcp4(ccp4: Ccp4File) {
+    return await volumeFromCcp4(ccp4).run()
 }

+ 98 - 0
src/apps/canvas/volume-view.ts

@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import Viewer from 'mol-view/viewer';
+import { BehaviorSubject } from 'rxjs';
+import { App } from './app';
+import { Progress } from 'mol-task';
+import { VolumeData } from 'mol-model/volume';
+import { VolumeRepresentation } from 'mol-geo/representation/volume';
+import IsosurfaceVisual from 'mol-geo/representation/volume/isosurface';
+import { Vec3 } from 'mol-math/linear-algebra';
+
+export interface VolumeView {
+    readonly app: App
+    readonly viewer: Viewer
+
+    readonly label: string
+    readonly volume: VolumeData
+
+    readonly active: { [k: string]: boolean }
+    readonly volumeRepresentations: { [k: string]: VolumeRepresentation<any> }
+    readonly updated: BehaviorSubject<null>
+
+    setVolumeRepresentation(name: string, value: boolean): void
+    destroy: () => void
+}
+
+interface StructureViewProps {
+    assemblyId?: string
+    symmetryFeatureId?: number
+}
+
+export async function VolumeView(app: App, viewer: Viewer, volume: VolumeData, props: StructureViewProps = {}): Promise<VolumeView> {
+    const active: { [k: string]: boolean } = {
+        isosurface: true,
+        volume: false,
+    }
+
+    const volumeRepresentations: { [k: string]: VolumeRepresentation<any> } = {
+        isosurface: VolumeRepresentation(IsosurfaceVisual),
+    }
+
+    const updated: BehaviorSubject<null> = new BehaviorSubject<null>(null)
+
+    let label: string = 'Volume'
+
+    async function setVolumeRepresentation(k: string, value: boolean) {
+        active[k] = value
+        await createVolumeRepr()
+    }
+
+    async function createVolumeRepr() {
+        for (const k in volumeRepresentations) {
+            if (active[k]) {
+                await app.runTask(volumeRepresentations[k].createOrUpdate({}, volume).run(
+                    progress => console.log(Progress.format(progress))
+                ), 'Create/update representation')
+                viewer.add(volumeRepresentations[k])
+            } else {
+                viewer.remove(volumeRepresentations[k])
+            }
+        }
+
+        // const center = Vec3.clone(volume.cell.size)
+        // Vec3.scale(center, center, 0.5)
+        // viewer.center(center)
+
+        updated.next(null)
+        viewer.requestDraw(true)
+        console.log('stats', viewer.stats)
+    }
+
+    await createVolumeRepr()
+
+    return {
+        app,
+        viewer,
+
+        get label() { return label },
+        volume,
+
+        active,
+        volumeRepresentations,
+        setVolumeRepresentation,
+        updated,
+
+        destroy: () => {
+            for (const k in volumeRepresentations) {
+                viewer.remove(volumeRepresentations[k])
+                volumeRepresentations[k].destroy()
+            }
+            viewer.requestDraw(true)
+        }
+    }
+}

+ 21 - 11
src/mol-geo/representation/volume/index.ts

@@ -5,7 +5,6 @@
  */
 
 import { Task } from 'mol-task'
-import { RenderObject } from 'mol-gl/render-object';
 import { RepresentationProps, Representation, Visual } from '..';
 import { VolumeData } from 'mol-model/volume';
 import { PickingId } from '../../geometry/picking';
@@ -13,32 +12,41 @@ import { Loci, EmptyLoci } from 'mol-model/loci';
 import { MarkerAction } from '../../geometry/marker-data';
 import { Geometry } from '../../geometry/geometry';
 import { paramDefaultValues } from 'mol-view/parameter';
+import { IsosurfaceParams } from './isosurface';
 
 export interface VolumeVisual<P extends RepresentationProps = {}> extends Visual<VolumeData, P> { }
 
 export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { }
 
 export const VolumeParams = {
-    ...Geometry.Params
+    ...Geometry.Params,
+    ...IsosurfaceParams
 }
 export const DefaultVolumeProps = paramDefaultValues(VolumeParams)
 export type VolumeProps = typeof DefaultVolumeProps
 
 export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
-    const renderObjects: RenderObject[] = []
-    let _volumeData: VolumeData
+    let visual: VolumeVisual<any>
     let _props: P
+    let busy = false
 
     function createOrUpdate(props: Partial<P> = {}, volumeData?: VolumeData) {
         _props = Object.assign({}, DefaultVolumeProps, _props, props)
         return Task.create('VolumeRepresentation.create', async ctx => {
-            if (volumeData) {
-                _volumeData = volumeData
-                const visual = visualCtor(_volumeData)
-                await visual.createOrUpdate(ctx, props, _volumeData)
-                if (visual.renderObject) renderObjects.push(visual.renderObject)
+            // TODO queue it somehow
+            if (busy) return
+
+            if (!visual && !volumeData) {
+                throw new Error('volumeData missing')
+            } else if (volumeData && !visual) {
+                busy = true
+                visual = visualCtor(volumeData)
+                await visual.createOrUpdate(ctx, props, volumeData)
+                busy = false
             } else {
-                throw new Error('missing volumeData')
+                busy = true
+                await visual.createOrUpdate(ctx, props, volumeData)
+                busy = false
             }
         });
     }
@@ -46,7 +54,9 @@ export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeD
     return {
         label: 'Volume mesh',
         params: VolumeParams,
-        get renderObjects () { return renderObjects },
+        get renderObjects() {
+            return visual && visual.renderObject ? [ visual.renderObject ] : []
+        },
         get props () { return _props },
         createOrUpdate,
         getLoci(pickingId: PickingId) {

+ 56 - 23
src/mol-geo/representation/volume/isosurface.ts

@@ -17,58 +17,91 @@ import { Loci, EmptyLoci } from 'mol-model/loci';
 import { LocationIterator } from '../../util/location-iterator';
 import { NullLocation } from 'mol-model/location';
 import { createIdentityTransform } from '../../geometry/transform-data';
-import { createRenderableState } from '../../geometry/geometry';
+import { createRenderableState, updateRenderableState } from '../../geometry/geometry';
 import { paramDefaultValues, NumberParam } from 'mol-view/parameter';
+import { ValueCell } from 'mol-util';
 
-export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) {
+export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue, mesh?: Mesh) {
     return Task.create<Mesh>('Volume Surface', async ctx => {
         ctx.update({ message: 'Marching cubes...' });
 
-        const mesh = await computeMarchingCubesMesh({
+        const surface = await computeMarchingCubesMesh({
             isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue,
             scalarField: volume.data
-        }).runAsChild(ctx);
+        }, mesh).runAsChild(ctx);
 
         const transform = VolumeData.getGridToCartesianTransform(volume);
         ctx.update({ message: 'Transforming mesh...' });
-        Mesh.transformImmediate(mesh, transform);
+        Mesh.transformImmediate(surface, transform);
+        Mesh.computeNormalsImmediate(surface)
 
-        return mesh;
+        return surface;
     });
 }
 
 export const IsosurfaceParams = {
     ...Mesh.Params,
-    isoValue: NumberParam('Iso Value', '', 2, -5, 5, 0.01),
+    isoValue: NumberParam('Iso Value', '', 2, -15, 15, 0.01),
 }
 export const DefaultIsosurfaceProps = paramDefaultValues(IsosurfaceParams)
 export type IsosurfaceProps = typeof DefaultIsosurfaceProps
 
 export default function IsosurfaceVisual(): VolumeVisual<IsosurfaceProps> {
-    let renderObject: MeshRenderObject
     let currentProps = DefaultIsosurfaceProps
+    let renderObject: MeshRenderObject
+    let currentVolume: VolumeData
+    let mesh: Mesh
+
+    async function create(ctx: RuntimeContext, volume: VolumeData, props: Partial<IsosurfaceProps> = {}) {
+        currentProps = { ...DefaultIsosurfaceProps, ...props }
+
+        mesh = await computeVolumeSurface(volume,  VolumeIsoValue.relative(volume.dataStats, currentProps.isoValue)).runAsChild(ctx)
+
+        const locationIt = LocationIterator(1, 1, () => NullLocation)
+        const transform = createIdentityTransform()
+
+        const values = await Mesh.createValues(ctx, mesh, transform, locationIt, currentProps)
+        const state = createRenderableState(currentProps)
+
+        renderObject = createMeshRenderObject(values, state)
+    }
+
+    async function update(ctx: RuntimeContext, props: Partial<IsosurfaceProps> = {}) {
+        const newProps = { ...currentProps, ...props }
+
+        let createMesh = false
+
+        if (newProps.isoValue !== currentProps.isoValue) createMesh = true
+
+        if (createMesh) {
+            mesh = await computeVolumeSurface(currentVolume,  VolumeIsoValue.relative(currentVolume.dataStats, currentProps.isoValue), mesh).runAsChild(ctx)
+            ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+        }
+
+        Mesh.updateValues(renderObject.values, newProps)
+        updateRenderableState(renderObject.state, newProps)
+
+        currentProps = newProps
+        return true
+    }
 
     return {
         get renderObject () { return renderObject },
         async createOrUpdate(ctx: RuntimeContext, props: Partial<IsosurfaceProps> = {}, volume?: VolumeData) {
-            currentProps = { ...DefaultIsosurfaceProps, ...props }
-
-            console.log('MOINMOIN')
-            if (!volume) return
-
-            const mesh = await computeVolumeSurface(volume,  VolumeIsoValue.relative(volume.dataStats, currentProps.isoValue)).runAsChild(ctx)
-            if (!props.flatShaded) {
-                Mesh.computeNormalsImmediate(mesh)
+            if (!volume && !currentVolume) {
+                throw new Error('missing volume')
+            } else if (volume && (!currentVolume || !renderObject)) {
+                currentVolume = volume
+                await create(ctx, volume, props)
+            } else if (volume && volume !== currentVolume) {
+                currentVolume = volume
+                await create(ctx, volume, props)
+            } else {
+                await update(ctx, props)
             }
 
-            const locationIt = LocationIterator(1, 1, () => NullLocation)
-            const transform = createIdentityTransform()
-
-            const values = await Mesh.createValues(ctx, mesh, transform, locationIt, currentProps)
-            const state = createRenderableState(currentProps)
+            currentProps = { ...DefaultIsosurfaceProps, ...props }
 
-            renderObject = createMeshRenderObject(values, state)
-            console.log('renderObject', renderObject)
         },
         getLoci(pickingId: PickingId) {
             // TODO

+ 66 - 0
src/mol-math/linear-algebra/3d/mat4.ts

@@ -176,6 +176,72 @@ namespace Mat4 {
         return Mat4.copy(Mat4.zero(), a);
     }
 
+    /**
+     * Returns the translation vector component of a transformation matrix.
+     */
+    export function getTranslation(out: Vec3, mat: Mat4) {
+        out[0] = mat[12];
+        out[1] = mat[13];
+        out[2] = mat[14];
+        return out;
+    }
+
+    /**
+     * Returns the scaling factor component of a transformation matrix.
+     */
+    export function getScaling(out: Vec3, mat: Mat4) {
+        let m11 = mat[0];
+        let m12 = mat[1];
+        let m13 = mat[2];
+        let m21 = mat[4];
+        let m22 = mat[5];
+        let m23 = mat[6];
+        let m31 = mat[8];
+        let m32 = mat[9];
+        let m33 = mat[10];
+        out[0] = Math.sqrt(m11 * m11 + m12 * m12 + m13 * m13);
+        out[1] = Math.sqrt(m21 * m21 + m22 * m22 + m23 * m23);
+        out[2] = Math.sqrt(m31 * m31 + m32 * m32 + m33 * m33);
+        return out;
+    }
+
+    /**
+     * Returns a quaternion representing the rotational component of a transformation matrix.
+     */
+    export function getRotation(out: Quat, mat: Mat4) {
+        // Algorithm taken from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+        let trace = mat[0] + mat[5] + mat[10];
+        let S = 0;
+
+        if (trace > 0) {
+            S = Math.sqrt(trace + 1.0) * 2;
+            out[3] = 0.25 * S;
+            out[0] = (mat[6] - mat[9]) / S;
+            out[1] = (mat[8] - mat[2]) / S;
+            out[2] = (mat[1] - mat[4]) / S;
+        } else if ((mat[0] > mat[5]) && (mat[0] > mat[10])) {
+            S = Math.sqrt(1.0 + mat[0] - mat[5] - mat[10]) * 2;
+            out[3] = (mat[6] - mat[9]) / S;
+            out[0] = 0.25 * S;
+            out[1] = (mat[1] + mat[4]) / S;
+            out[2] = (mat[8] + mat[2]) / S;
+        } else if (mat[5] > mat[10]) {
+            S = Math.sqrt(1.0 + mat[5] - mat[0] - mat[10]) * 2;
+            out[3] = (mat[8] - mat[2]) / S;
+            out[0] = (mat[1] + mat[4]) / S;
+            out[1] = 0.25 * S;
+            out[2] = (mat[6] + mat[9]) / S;
+        } else {
+            S = Math.sqrt(1.0 + mat[10] - mat[0] - mat[5]) * 2;
+            out[3] = (mat[1] - mat[4]) / S;
+            out[0] = (mat[8] + mat[2]) / S;
+            out[1] = (mat[6] + mat[9]) / S;
+            out[2] = 0.25 * S;
+        }
+
+        return out;
+    }
+
     export function transpose(out: Mat4, a: Mat4) {
         // If we are transposing ourselves we can skip a few steps but have to cache some values
         if (out === a) {