From 730337aca5ecbc24b66db0933b72c31a8ad9c67e Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Wed, 11 Apr 2018 18:25:43 -0700 Subject: [PATCH] wip, react components --- package-lock.json | Bin 424340 -> 441292 bytes package.json | 4 +- .../render-test/components/file-input.tsx | 40 ++++ src/apps/render-test/components/observer.tsx | 21 ++ src/apps/render-test/components/viewport.tsx | 22 ++ src/apps/render-test/index.tsx | 2 +- src/apps/render-test/state.ts | 225 +++++++++--------- src/apps/render-test/ui.tsx | 75 +++++- src/apps/render-test/utils/index.ts | 27 +++ src/apps/render-test/{ => utils}/mcubes.ts | 0 src/mol-gl/camera.ts | 5 +- src/mol-gl/renderable/mesh.ts | 1 - src/mol-gl/renderable/point.ts | 1 - src/mol-gl/renderer.ts | 57 +++-- 14 files changed, 339 insertions(+), 141 deletions(-) create mode 100644 src/apps/render-test/components/file-input.tsx create mode 100644 src/apps/render-test/components/observer.tsx create mode 100644 src/apps/render-test/components/viewport.tsx create mode 100644 src/apps/render-test/utils/index.ts rename src/apps/render-test/{ => utils}/mcubes.ts (100%) diff --git a/package-lock.json b/package-lock.json index adc4e46e5fb1e29dd1bde04c49b8f6cce5f3e0de..4ce0ce891139fdffd9528576f7e5180db3a8fb39 100644 GIT binary patch delta 9207 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 delta 488 zcmX@}U24i_$ql!8HyiOMvP?fWgH3btZ9c)xyM(7PZr&pnzF|7=Ay&=l2QD&lO`nj> zCb-$<IEUP3n`fm8+pF0bnOU}b@G#m5Pj6qqEIc`HBhT~+=a^ZhADGByw0))mV~W}2 z6c>f<T;Ys!c(*S~Wn95Ded<I;h3(-Pj7xd8e=TLK<K3>+%4l_Cdt^M5Md<e2txUYE z(>>alMW+AFVnH_S#~!8*Hb#r-jfa_1r@y|#%)7npC{wEB_Br>N9y3mt+{7w4`5haQ zhUvZwnZ&lQf5gPdF+I<aQEU4LVHQos?KfXDh39Xdp2M8W$!M{iwTyW_>vUfmCX4MC zCNuwF+-_daEGYwaa_nqo6Oru?wllAn+}?MISwesNVJ4O{e%pP5SQ?bJuP<e>5Zu0^ zpM_Uo`v0XY?Az}xW)Xt=?e9{SNvh1IdZv>bM2)Bat!5UOd`M6^)Tzq6Fu&A1%+WL7 zJu^MWD=MifDI(FNtRTYFDK{j<&&VZ9zc|%QJGk7@rKH>>I5VTr$urY6(L5|SB*M$0 zpfazl&>+*T$ke^UGR&v4GS#rivts(g%S>j|*Y0K#nas~Gv;FW{76-$v8m#*9+c^_h TZ#ZwyU&_iAw>_JMEpa;lZA7<H diff --git a/package.json b/package.json index a663aa7ad..70f4cd415 100644 --- a/package.json +++ b/package.json @@ -80,8 +80,10 @@ "dependencies": { "argparse": "^1.0.10", "express": "^4.16.3", + "material-ui": "^1.0.0-beta.41", "node-fetch": "^2.1.2", "react": "^16.3.1", - "react-dom": "^16.3.1" + "react-dom": "^16.3.1", + "rxjs": "^6.0.0-beta.4" } } diff --git a/src/apps/render-test/components/file-input.tsx b/src/apps/render-test/components/file-input.tsx new file mode 100644 index 000000000..f83d200ab --- /dev/null +++ b/src/apps/render-test/components/file-input.tsx @@ -0,0 +1,40 @@ +/** + * 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 { WithStyles } from 'material-ui/styles'; +import TextField from 'material-ui/TextField'; +// import FileUpload from '@material-ui/icons/FileUpload'; + +import State from '../state' +import Observer from './observer'; + +export default class FileInput extends Observer<{ state: State } & WithStyles, { loading: boolean }> { + state = { loading: false } + + componentDidMount() { + this.subscribe(this.props.state.loading, value => { + this.setState({ loading: value }); + }); + } + + render() { + const { classes, state } = this.props; + + return <TextField + label='PDB ID' + className={classes.textField} + disabled={this.state.loading} + margin='normal' + onChange={(event) => { + state.pdbId = event.target.value + }} + onKeyPress={(event) => { + if (event.key === 'Enter') state.loadPdbId() + }} + /> + } +} \ No newline at end of file diff --git a/src/apps/render-test/components/observer.tsx b/src/apps/render-test/components/observer.tsx new file mode 100644 index 000000000..67e48fb23 --- /dev/null +++ b/src/apps/render-test/components/observer.tsx @@ -0,0 +1,21 @@ +/** + * 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 { Observable, Subscription } from 'rxjs'; + +export default class Observer<S, P> extends React.Component<S, P> { + private _subs: Subscription[] = [] + + subscribe<T>(obs: Observable<T>, onNext: (v: T) => void) { + this._subs.push(obs.subscribe(onNext)); + } + + componentWillUnmount() { + for (const s of this._subs) s.unsubscribe(); + this._subs = []; + } +} \ No newline at end of file diff --git a/src/apps/render-test/components/viewport.tsx b/src/apps/render-test/components/viewport.tsx new file mode 100644 index 000000000..1536cd5a4 --- /dev/null +++ b/src/apps/render-test/components/viewport.tsx @@ -0,0 +1,22 @@ +/** + * 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 State from '../state' + +export default class Viewport extends React.Component<{ state: State }, { initialized: boolean }> { + private canvasContainer: HTMLDivElement | null = null; + state = { initialized: false } + + componentDidMount() { + if (this.canvasContainer) this.props.state.initRenderer(this.canvasContainer).then(() => this.setState({ initialized: true })) + } + + render() { + return <div ref={elm => this.canvasContainer = elm} style={{ height: '100%' }}> + </div> + } +} \ No newline at end of file diff --git a/src/apps/render-test/index.tsx b/src/apps/render-test/index.tsx index 246502b30..2e87d291f 100644 --- a/src/apps/render-test/index.tsx +++ b/src/apps/render-test/index.tsx @@ -10,4 +10,4 @@ import * as React from 'react' import * as ReactDOM from 'react-dom' const state = new State() -ReactDOM.render(<UI state={state} />, document.getElementById('app')); \ No newline at end of file +ReactDOM.render(<UI state={ state } />, document.getElementById('app')); \ No newline at end of file diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts index bfc4e2a2a..abb3245e4 100644 --- a/src/apps/render-test/state.ts +++ b/src/apps/render-test/state.ts @@ -4,127 +4,47 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { ValueCell } from 'mol-util/value-cell' +import { BehaviorSubject } from 'rxjs'; -import { Vec3, Mat4 } from 'mol-math/linear-algebra' -import { createRenderer, createRenderObject } from 'mol-gl/renderer' -import { createColorTexture } from 'mol-gl/util'; +// import { ValueCell } from 'mol-util/value-cell' + +// import { Vec3, Mat4 } from 'mol-math/linear-algebra' +import { createRenderer, Renderer } from 'mol-gl/renderer' +// import { createColorTexture } from 'mol-gl/util'; // import Icosahedron from 'mol-geo/primitive/icosahedron' // import Box from 'mol-geo/primitive/box' import Spacefill from 'mol-geo/representation/structure/spacefill' import Point from 'mol-geo/representation/structure/point' -import CIF from 'mol-io/reader/cif' -import { Run, Progress } from 'mol-task' -import { Structure, Symmetry } from 'mol-model/structure' - -function log(progress: Progress) { - const p = progress.root.progress - console.log(`${p.message} ${(p.current/p.max*100).toFixed(2)}%`) -} - -async function parseCif(data: string|Uint8Array) { - const comp = CIF.parse(data) - const parsed = await Run(comp, log, 100); - if (parsed.isError) throw parsed; - return parsed -} - -async function getPdb(pdb: string) { - const data = await fetch(`https://files.rcsb.org/download/${pdb}.cif`) - const parsed = await parseCif(await data.text()) - const structure = Structure.ofData({ kind: 'mmCIF', data: CIF.schema.mmCIF(parsed.result.blocks[0]) }) - return structure -} +import { Run } from 'mol-task' +import { Symmetry } from 'mol-model/structure' -import mcubes from './mcubes' +// import mcubes from './utils/mcubes' +import { getStructuresFromPdbId } from './utils' import { StructureRepresentation } from 'mol-geo/representation/structure'; // import Cylinder from 'mol-geo/primitive/cylinder'; export default class State { + renderer: Renderer + pdbId = '1crn' + initialized = new BehaviorSubject<boolean>(false) + loading = new BehaviorSubject<boolean>(false) + async initRenderer (container: HTMLDivElement) { - const renderer = createRenderer(container) - - const p1 = Vec3.create(0, 4, 0) - const p2 = Vec3.create(-3, 0, 0) - - // const position = ValueCell.create(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0])) - // const normal = ValueCell.create(new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0])) - - const transformArray1 = ValueCell.create(new Float32Array(16)) - const transformArray2 = ValueCell.create(new Float32Array(16 * 3)) - const m4 = Mat4.identity() - Mat4.toArray(m4, transformArray1.ref.value, 0) - Mat4.toArray(m4, transformArray2.ref.value, 0) - Mat4.setTranslation(m4, p1) - Mat4.toArray(m4, transformArray2.ref.value, 16) - Mat4.setTranslation(m4, p2) - Mat4.toArray(m4, transformArray2.ref.value, 32) - - const color = ValueCell.create(createColorTexture(3)) - color.ref.value.set([ - 0, 0, 255, - 0, 255, 0, - 255, 0, 0 - ]) - - // const points = createRenderObject('point', { - // position, - // transform: transformArray1 - // }) - // // renderer.add(points) - - // const mesh = createRenderObject('mesh', { - // position, - // normal, - // color, - // transform: transformArray2 - // }) - // renderer.add(mesh) - - // const cylinder = Cylinder({ height: 3, radiusBottom: 0.5, radiusTop: 0.5 }) - // console.log(cylinder) - // const cylinderMesh = createRenderObject('mesh', { - // position: ValueCell.create(cylinder.vertices), - // normal: ValueCell.create(cylinder.normals), - // color, - // transform: transformArray2 - // }, cylinder.indices) - // renderer.add(cylinderMesh) - - // const sphere = Icosahedron() - // console.log(sphere) - - // const box = Box() - // console.log(box) - - // const points2 = createRenderObject('point', { - // position: ValueCell.create(new Float32Array(box.vertices)), - // transform: transformArray1 - // }) - // renderer.add(points2) - - let rr = 0.7; - function cubesF(x: number, y: number, z: number) { - return x * x + y * y + z * z - rr * rr; - } - let cubes = await mcubes(cubesF); - - const makeCubesMesh = () => createRenderObject('mesh', { - position: cubes.surface.vertexBuffer, - normal: cubes.surface.normalBuffer, - color, - transform: transformArray2, - elements: cubes.surface.indexBuffer, - - instanceCount: transformArray2.ref.value.length / 16, - elementCount: cubes.surface.triangleCount, - positionCount: cubes.surface.vertexCount - }, {}); - const mesh2 = makeCubesMesh(); - renderer.add(mesh2) - - const structures = await getPdb('1rb8') + this.renderer = createRenderer(container) + this.initialized.next(true) + this.loadPdbId() + this.renderer.frame() + } + + async loadPdbId () { + const { renderer, pdbId } = this + renderer.clear() + + if (pdbId.length !== 4) return + this.loading.next(true) + + const structures = await getStructuresFromPdbId(pdbId) const struct = Symmetry.buildAssembly(structures[0], '1') const structPointRepr = StructureRepresentation(Point) @@ -135,6 +55,91 @@ export default class State { await Run(structSpacefillRepr.create(struct)) structSpacefillRepr.renderObjects.forEach(renderer.add) - renderer.frame() + renderer.draw(true) + + this.loading.next(false) } } + + + +// async foo () { +// const p1 = Vec3.create(0, 4, 0) +// const p2 = Vec3.create(-3, 0, 0) + +// // const position = ValueCell.create(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0])) +// // const normal = ValueCell.create(new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0])) + +// const transformArray1 = ValueCell.create(new Float32Array(16)) +// const transformArray2 = ValueCell.create(new Float32Array(16 * 3)) +// const m4 = Mat4.identity() +// Mat4.toArray(m4, transformArray1.ref.value, 0) +// Mat4.toArray(m4, transformArray2.ref.value, 0) +// Mat4.setTranslation(m4, p1) +// Mat4.toArray(m4, transformArray2.ref.value, 16) +// Mat4.setTranslation(m4, p2) +// Mat4.toArray(m4, transformArray2.ref.value, 32) + +// const color = ValueCell.create(createColorTexture(3)) +// color.ref.value.set([ +// 0, 0, 255, +// 0, 255, 0, +// 255, 0, 0 +// ]) + +// // const points = createRenderObject('point', { +// // position, +// // transform: transformArray1 +// // }) +// // // renderer.add(points) + +// // const mesh = createRenderObject('mesh', { +// // position, +// // normal, +// // color, +// // transform: transformArray2 +// // }) +// // renderer.add(mesh) + +// // const cylinder = Cylinder({ height: 3, radiusBottom: 0.5, radiusTop: 0.5 }) +// // console.log(cylinder) +// // const cylinderMesh = createRenderObject('mesh', { +// // position: ValueCell.create(cylinder.vertices), +// // normal: ValueCell.create(cylinder.normals), +// // color, +// // transform: transformArray2 +// // }, cylinder.indices) +// // renderer.add(cylinderMesh) + +// // const sphere = Icosahedron() +// // console.log(sphere) + +// // const box = Box() +// // console.log(box) + +// // const points2 = createRenderObject('point', { +// // position: ValueCell.create(new Float32Array(box.vertices)), +// // transform: transformArray1 +// // }) +// // renderer.add(points2) + +// // let rr = 0.7; +// // function cubesF(x: number, y: number, z: number) { +// // return x * x + y * y + z * z - rr * rr; +// // } +// // let cubes = await mcubes(cubesF); + +// // const makeCubesMesh = () => createRenderObject('mesh', { +// // position: cubes.surface.vertexBuffer, +// // normal: cubes.surface.normalBuffer, +// // color, +// // transform: transformArray2, +// // elements: cubes.surface.indexBuffer, + +// // instanceCount: transformArray2.ref.value.length / 16, +// // elementCount: cubes.surface.triangleCount, +// // positionCount: cubes.surface.vertexCount +// // }, {}); +// // const mesh2 = makeCubesMesh(); +// // renderer.add(mesh2) +// } \ No newline at end of file diff --git a/src/apps/render-test/ui.tsx b/src/apps/render-test/ui.tsx index dbdf41f3b..e0ca7ec60 100644 --- a/src/apps/render-test/ui.tsx +++ b/src/apps/render-test/ui.tsx @@ -5,18 +5,75 @@ */ import * as React from 'react' +import { withStyles, WithStyles, Theme, StyleRulesCallback } from 'material-ui/styles'; +import Typography from 'material-ui/Typography'; +import Toolbar from 'material-ui/Toolbar'; +import AppBar from 'material-ui/AppBar'; +import Drawer from 'material-ui/Drawer'; + +import Viewport from './components/viewport' +import FileInput from './components/file-input' import State from './state' -export default class Root extends React.Component<{ state: State }, { initialized: boolean }> { - private canvasContainer: HTMLDivElement | null = null; - state = { initialized: false } +const styles: StyleRulesCallback<any> = (theme: Theme) => ({ + root: { + flexGrow: 1, + height: 830, + zIndex: 1, + overflow: 'hidden', + position: 'relative', + display: 'flex', + }, + appBar: { + zIndex: theme.zIndex.drawer + 1, + }, + drawerPaper: { + position: 'relative', + width: 240, + }, + content: { + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: theme.spacing.unit * 3, + minWidth: 0, // So the Typography noWrap works + }, + toolbar: theme.mixins.toolbar, + textField: { + marginLeft: theme.spacing.unit, + marginRight: theme.spacing.unit, + width: 200, + }, +}); - componentDidMount() { - if (this.canvasContainer) this.props.state.initRenderer(this.canvasContainer).then(() => this.setState({ initialized: true })) - } +const decorate = withStyles(styles); +interface Props { + state: State; +}; + +class UI extends React.Component<{ state: State } & WithStyles, { }> { render() { - return <div ref={elm => this.canvasContainer = elm} style={{ position: 'absolute', top: 0, right: 0, left: 0, bottom: 0, overflow: 'hidden' }}> - </div> + const { classes, state } = this.props; + return ( + <div className={classes.root}> + <AppBar position='absolute' className={classes.appBar}> + <Toolbar> + <Typography variant='title' color='inherit' noWrap> + Mol* Render Test + </Typography> + </Toolbar> + </AppBar> + <Drawer variant='permanent' classes={{ paper: classes.drawerPaper, }}> + <div className={classes.toolbar} /> + <FileInput state={state} classes={classes}></FileInput> + </Drawer> + <main className={classes.content}> + <div className={classes.toolbar} /> + <Viewport state={state}></Viewport> + </main> + </div> + ); } -} \ No newline at end of file +} + +export default decorate<Props>(UI) \ No newline at end of file diff --git a/src/apps/render-test/utils/index.ts b/src/apps/render-test/utils/index.ts new file mode 100644 index 000000000..67a7eb9a3 --- /dev/null +++ b/src/apps/render-test/utils/index.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import CIF from 'mol-io/reader/cif' +import { Run, Progress } from 'mol-task' +import { Structure } from 'mol-model/structure' + +export function log(progress: Progress) { + const p = progress.root.progress + console.log(`${p.message} ${(p.current/p.max*100).toFixed(2)}%`) +} + +export async function parseCif(data: string|Uint8Array) { + const comp = CIF.parse(data) + const parsed = await Run(comp, log, 100); + if (parsed.isError) throw parsed; + return parsed +} + +export async function getStructuresFromPdbId(pdbid: string) { + const data = await fetch(`https://files.rcsb.org/download/${pdbid}.cif`) + const parsed = await parseCif(await data.text()) + return Structure.ofData({ kind: 'mmCIF', data: CIF.schema.mmCIF(parsed.result.blocks[0]) }) +} \ No newline at end of file diff --git a/src/apps/render-test/mcubes.ts b/src/apps/render-test/utils/mcubes.ts similarity index 100% rename from src/apps/render-test/mcubes.ts rename to src/apps/render-test/utils/mcubes.ts diff --git a/src/mol-gl/camera.ts b/src/mol-gl/camera.ts index f3a3ffa04..623e24672 100644 --- a/src/mol-gl/camera.ts +++ b/src/mol-gl/camera.ts @@ -49,7 +49,7 @@ export interface Camera { update: (props: any, block: any) => void, setState: (newState: CameraState) => void, getState: () => CameraState, - isDirty: () => boolean + dirty: boolean } export namespace Camera { @@ -185,7 +185,8 @@ export namespace Camera { update, setState, getState: () => Object.assign({}, state), - isDirty: () => dirty + get dirty() { return dirty }, + set dirty(value: boolean) { dirty = value } } } } diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index a7fa373f9..52c2244c7 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -31,7 +31,6 @@ namespace Mesh { } export function create(regl: REGL.Regl, data: Data, uniforms: Uniforms): Renderable { - console.log(data) const instanceId = ValueCell.create(fillSerial(new Float32Array(data.instanceCount))) const command = regl({ ...MeshShaders, diff --git a/src/mol-gl/renderable/point.ts b/src/mol-gl/renderable/point.ts index 99b311655..5bc8aed30 100644 --- a/src/mol-gl/renderable/point.ts +++ b/src/mol-gl/renderable/point.ts @@ -24,7 +24,6 @@ namespace Point { } export function create(regl: REGL.Regl, data: Data): Renderable { - console.log(data) const instanceId = ValueCell.create(fillSerial(new Float32Array(data.instanceCount))) const command = regl({ ...PointShaders, diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index f4c25f0f6..eef94cf27 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -39,7 +39,8 @@ export function createRenderObject(type: 'mesh' | 'point', data: PointRenderable export interface Renderer { add: (o: RenderObject) => void remove: (o: RenderObject) => void - draw: () => void + clear: () => void + draw: (force: boolean) => void frame: () => void } @@ -54,18 +55,33 @@ export function createRenderer(container: HTMLDivElement): Renderer { const renderableList: Renderable[] = [] const objectIdRenderableMap: { [k: number]: Renderable } = {} - const regl = glContext.create({ - container, - extensions: [ - 'OES_texture_float', - 'OES_texture_float_linear', - 'OES_element_index_uint', - // 'EXT_disjoint_timer_query', - 'EXT_blend_minmax', - 'ANGLE_instanced_arrays' - ], - profile: true - }) + let regl: REGL.Regl + try { + regl = glContext.create({ + container, + extensions: [ + 'OES_texture_float', + 'OES_texture_float_linear', + 'OES_element_index_uint', + 'EXT_disjoint_timer_query', + 'EXT_blend_minmax', + 'ANGLE_instanced_arrays' + ], + profile: true + }) + } catch (e) { + regl = glContext.create({ + container, + extensions: [ + 'OES_texture_float', + 'OES_texture_float_linear', + 'OES_element_index_uint', + 'EXT_blend_minmax', + 'ANGLE_instanced_arrays' + ], + profile: true + }) + } const camera = Camera.create(regl, container, { center: Vec3.create(0, 0, 0), @@ -91,12 +107,12 @@ export function createRenderer(container: HTMLDivElement): Renderer { } }) - const draw = () => { + const draw = (force = false) => { camera.update((state: any) => { - if (!camera.isDirty()) return; + if (!force && !camera.dirty) return; baseContext(() => { // console.log(ctx) - regl.clear({color: [0, 0, 0, 1]}) + regl.clear({ color: [0, 0, 0, 1] }) // TODO painters sort, filter visible, filter picking, visibility culling? renderableList.forEach(r => { r.draw() @@ -118,6 +134,15 @@ export function createRenderer(container: HTMLDivElement): Renderer { delete objectIdRenderableMap[o.id] } }, + clear: () => { + for (const id in objectIdRenderableMap) { + // TODO + // objectIdRenderableMap[id].destroy() + delete objectIdRenderableMap[id] + } + renderableList.length = 0 + camera.dirty = true + }, draw, frame: () => { regl.frame((ctx) => draw()) -- GitLab