diff --git a/package-lock.json b/package-lock.json index 3b08f5a912fe17a5092b48ab04f33b844ec2569c..6f49e4eb57913c4f02a5937dc4225fa292c0b0b8 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 4ce831040e6a5d96ab5d711722144b8b114e383e..29a84d8e89a5a343655d1d7dcb34d2c7a6cfd956 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "argparse": "^1.0.10", "express": "^4.16.3", "gl": "^4.0.4", - "material-ui": "^1.0.0-beta.41", + "material-ui": "^1.0.0-beta.43", "node-fetch": "^2.1.2", "react": "^16.3.2", "react-dom": "^16.3.2", diff --git a/src/apps/render-test/components/color-theme.tsx b/src/apps/render-test/components/color-theme.tsx new file mode 100644 index 0000000000000000000000000000000000000000..138d23008640390ca8c5174367074599d392a1ad --- /dev/null +++ b/src/apps/render-test/components/color-theme.tsx @@ -0,0 +1,60 @@ +/** + * 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 { MenuItem } from 'material-ui/Menu'; +import { InputLabel } from 'material-ui/Input'; +import { FormControl } from 'material-ui/Form'; +import Select from 'material-ui/Select'; + +import State, { ColorTheme as _ColorTheme } from '../state' +import Observer from './observer'; + +interface ColorThemeState { + loading: boolean + name: _ColorTheme +} + +export default class ColorTheme extends Observer<{ state: State } & WithStyles, ColorThemeState> { + state = { loading: false, name: 'element-symbol' as _ColorTheme } + + componentDidMount() { + this.subscribe(this.props.state.loading, value => { + this.setState({ loading: value }); + }); + this.subscribe(this.props.state.colorTheme, value => { + this.setState({ name: value }); + }); + } + + handleNameChange = (event: React.ChangeEvent<any>) => { + this.props.state.colorTheme.next(event.target.value) + } + + render() { + const { classes } = this.props; + + const items = Object.keys(_ColorTheme).map((name, idx) => { + return <MenuItem key={idx} value={name}>{name}</MenuItem> + }) + + return <FormControl className={classes.formControl}> + <InputLabel htmlFor='color-theme-name'>Color Theme</InputLabel> + <Select + className={classes.selectField} + value={this.state.name} + onChange={this.handleNameChange} + inputProps={{ + name: 'name', + id: 'color-theme-name', + }} + > + {items} + </Select> + </FormControl> + } +} \ No newline at end of file diff --git a/src/apps/render-test/components/detail.tsx b/src/apps/render-test/components/detail.tsx new file mode 100644 index 0000000000000000000000000000000000000000..65c5eb8e55732b4a54ff1a5e86d2399687935ccc --- /dev/null +++ b/src/apps/render-test/components/detail.tsx @@ -0,0 +1,60 @@ +/** + * 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 { MenuItem } from 'material-ui/Menu'; +import { InputLabel } from 'material-ui/Input'; +import { FormControl } from 'material-ui/Form'; +import Select from 'material-ui/Select'; + +import State from '../state' +import Observer from './observer'; + +interface DetailState { + loading: boolean + value: number +} + +export default class Detail extends Observer<{ state: State } & WithStyles, DetailState> { + state = { loading: false, value: 2 } + + componentDidMount() { + this.subscribe(this.props.state.loading, value => { + this.setState({ loading: value }); + }); + this.subscribe(this.props.state.detail, value => { + this.setState({ value }); + }); + } + + handleValueChange = (event: React.ChangeEvent<any>) => { + this.props.state.detail.next(event.target.value) + } + + render() { + const { classes } = this.props; + + const items = [0, 1, 2].map((value, idx) => { + return <MenuItem key={idx} value={value}>{value.toString()}</MenuItem> + }) + + return <FormControl className={classes.formControl}> + <InputLabel htmlFor='detail-value'>Detail</InputLabel> + <Select + className={classes.selectField} + value={this.state.value} + onChange={this.handleValueChange} + inputProps={{ + name: 'value', + id: 'detail-value', + }} + > + {items} + </Select> + </FormControl> + } +} \ No newline at end of file diff --git a/src/apps/render-test/components/visibility.tsx b/src/apps/render-test/components/visibility.tsx new file mode 100644 index 0000000000000000000000000000000000000000..86f9e44227e7749d352a9ca9580ae99165c75e50 --- /dev/null +++ b/src/apps/render-test/components/visibility.tsx @@ -0,0 +1,70 @@ +/** + * 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 { FormLabel, FormControl, FormGroup, FormControlLabel } from 'material-ui/Form'; +import Checkbox from 'material-ui/Checkbox'; + +import State from '../state' +import Observer from './observer'; + +interface VisibilityState { + loading: boolean + spacefill: boolean + point: boolean +} + +export default class Visibility extends Observer<{ state: State } & WithStyles, VisibilityState> { + state = { loading: false, spacefill: true, point: true } + + componentDidMount() { + this.subscribe(this.props.state.loading, value => { + this.setState({ loading: value }); + }); + this.subscribe(this.props.state.spacefillVisibility, value => { + this.setState({ spacefill: value }); + }); + this.subscribe(this.props.state.pointVisibility, value => { + this.setState({ point: value }); + }); + } + + handleChange = (event: React.ChangeEvent<any>) => { + switch (event.target.name) { + case 'point': this.props.state.pointVisibility.next(event.target.checked); break; + case 'spacefill': this.props.state.spacefillVisibility.next(event.target.checked); break; + } + } + + render() { + const { classes } = this.props + + return <div className={classes.formControl}> + <FormControl component='fieldset'> + <FormLabel component='legend'>Visibility</FormLabel> + <FormGroup> + <FormControlLabel + control={<Checkbox + checked={this.state.point} + onChange={this.handleChange} + name='point' + />} + label='Point' + /> + <FormControlLabel + control={<Checkbox + checked={this.state.spacefill} + onChange={this.handleChange} + name='spacefill' + />} + label='Spacefill' + /> + </FormGroup> + </FormControl> + </div> + } +} \ No newline at end of file diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts index e7cb9fab2bd392536b908297fbd57617312fbc24..d722048f4b85eca1cd31ca92e4f09c61f1ec92df 100644 --- a/src/apps/render-test/state.ts +++ b/src/apps/render-test/state.ts @@ -24,12 +24,52 @@ import { getStructuresFromPdbId, log } from './utils' import { StructureRepresentation } from 'mol-geo/representation/structure'; // import Cylinder from 'mol-geo/primitive/cylinder'; + +export const ColorTheme = { + 'atom-index': {}, + 'chain-id': {}, + 'element-symbol': {}, + 'instance-index': {} +} +export type ColorTheme = keyof typeof ColorTheme + export default class State { viewer: Viewer pdbId = '4cup' initialized = new BehaviorSubject<boolean>(false) loading = new BehaviorSubject<boolean>(false) + colorTheme = new BehaviorSubject<ColorTheme>('chain-id') + detail = new BehaviorSubject<number>(2) + + pointVisibility = new BehaviorSubject<boolean>(true) + spacefillVisibility = new BehaviorSubject<boolean>(true) + + pointRepr: StructureRepresentation<PointProps> + spacefillRepr: StructureRepresentation<SpacefillProps> + + constructor() { + this.colorTheme.subscribe(() => this.update()) + this.detail.subscribe(() => this.update()) + + this.pointVisibility.subscribe(() => this.updateVisibility()) + this.spacefillVisibility.subscribe(() => this.updateVisibility()) + } + + getSpacefillProps (): SpacefillProps { + return { + detail: this.detail.getValue(), + colorTheme: { name: this.colorTheme.getValue() }, + } + } + + getPointProps (): PointProps { + return { + colorTheme: { name: this.colorTheme.getValue() }, + sizeTheme: { name: 'uniform', value: 0.1 } + } + } + async initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) { this.viewer = Viewer.create(canvas, container) this.initialized.next(true) @@ -38,40 +78,57 @@ export default class State { } async loadPdbId () { - const { viewer, pdbId } = this + const { viewer, pdbId, loading } = this viewer.clear() if (pdbId.length !== 4) return - this.loading.next(true) + loading.next(true) const structures = await getStructuresFromPdbId(pdbId) const struct = await Run(Symmetry.buildAssembly(structures[0], '1'), log, 100) - const structPointRepr = StructureRepresentation(Point) - const pointProps: PointProps = { - // colorTheme: { name: 'uniform', value: 0xFF4411 }, - colorTheme: { name: 'chain-id' }, - sizeTheme: { name: 'uniform', value: 0.1 } - } - await Run(structPointRepr.create(struct, pointProps), log, 100) - structPointRepr.renderObjects.forEach(viewer.add) - - const structSpacefillRepr = StructureRepresentation(Spacefill) - const spacefillProps: SpacefillProps = { - detail: 1, - // colorTheme: { name: 'uniform', value: 0xFF4411 }, - // colorTheme: { name: 'instance-index' }, - // colorTheme: { name: 'element-symbol' }, - // colorTheme: { name: 'atom-index' }, - colorTheme: { name: 'chain-id' }, - } - await Run(structSpacefillRepr.create(struct, spacefillProps), log, 100) - structSpacefillRepr.renderObjects.forEach(viewer.add) + this.pointRepr = StructureRepresentation(Point) + await Run(this.pointRepr.create(struct, this.getPointProps()), log, 100) + viewer.add(this.pointRepr) + + this.spacefillRepr = StructureRepresentation(Spacefill) + await Run(this.spacefillRepr.create(struct, this.getSpacefillProps()), log, 100) + viewer.add(this.spacefillRepr) + this.updateVisibility() viewer.requestDraw() console.log(viewer.stats) - this.loading.next(false) + loading.next(false) + } + + async update () { + if (!this.spacefillRepr) return + await Run(this.spacefillRepr.update(this.getSpacefillProps()), log, 100) + await Run(this.pointRepr.update(this.getPointProps()), log, 100) + this.viewer.add(this.spacefillRepr) + this.viewer.add(this.pointRepr) + this.viewer.requestDraw() + console.log(this.viewer.stats) + } + + updateVisibility () { + if (!this.viewer) return + if (this.pointRepr) { + if (this.pointVisibility.getValue()) { + this.viewer.show(this.pointRepr) + } else { + this.viewer.hide(this.pointRepr) + } + } + if (this.spacefillRepr) { + if (this.spacefillVisibility.getValue()) { + this.viewer.show(this.spacefillRepr) + } else { + this.viewer.hide(this.spacefillRepr) + } + } + this.viewer.requestDraw() } } diff --git a/src/apps/render-test/ui.tsx b/src/apps/render-test/ui.tsx index e0ca7ec6086096cdf968bade7862f2c1a119615e..e65ea8e287ba2ab0667ccfc3edeb61bb3b90e873 100644 --- a/src/apps/render-test/ui.tsx +++ b/src/apps/render-test/ui.tsx @@ -11,11 +11,16 @@ import Toolbar from 'material-ui/Toolbar'; import AppBar from 'material-ui/AppBar'; import Drawer from 'material-ui/Drawer'; +import State from './state' + import Viewport from './components/viewport' import FileInput from './components/file-input' -import State from './state' +import ColorTheme from './components/color-theme' +import Detail from './components/detail' +import Visibility from './components/visibility' -const styles: StyleRulesCallback<any> = (theme: Theme) => ({ + +const styles: StyleRulesCallback = (theme: Theme) => ({ root: { flexGrow: 1, height: 830, @@ -38,12 +43,16 @@ const styles: StyleRulesCallback<any> = (theme: Theme) => ({ minWidth: 0, // So the Typography noWrap works }, toolbar: theme.mixins.toolbar, + formControl: { + margin: theme.spacing.unit, + width: 200, + }, textField: { marginLeft: theme.spacing.unit, marginRight: theme.spacing.unit, width: 200, }, -}); +} as any); const decorate = withStyles(styles); @@ -66,6 +75,13 @@ class UI extends React.Component<{ state: State } & WithStyles, { }> { <Drawer variant='permanent' classes={{ paper: classes.drawerPaper, }}> <div className={classes.toolbar} /> <FileInput state={state} classes={classes}></FileInput> + <form className={classes.root} autoComplete='off'> + <div> + <ColorTheme state={state} classes={classes}></ColorTheme> + <Detail state={state} classes={classes}></Detail> + <Visibility state={state} classes={classes}></Visibility> + </div> + </form> </Drawer> <main className={classes.content}> <div className={classes.toolbar} /> diff --git a/src/apps/structure-info/index.ts b/src/apps/structure-info/index.ts index 5233f459b883ed17617f53fff9391cb95ba1a944..077bb8a10b98b7288a7ffa102494d3079b9cdebe 100644 --- a/src/apps/structure-info/index.ts +++ b/src/apps/structure-info/index.ts @@ -22,7 +22,7 @@ async function parseCif(data: string|Uint8Array) { } async function getPdb(pdb: string) { - //const data = await fetch(`https://files.rcsb.org/download/${pdb}.cif`) + // const data = await fetch(`https://files.rcsb.org/download/${pdb}.cif`) const data = await fetch(`http://www.ebi.ac.uk/pdbe/static/entry/${pdb}_updated.cif`); const parsed = await parseCif(await data.text()) return CIF.schema.mmCIF(parsed.result.blocks[0]) diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 3bceeebe243884496fe5f3197a0d9b456c9f9428..5d722cec7c878ac4d381e580b812138ba9778c45 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -15,26 +15,32 @@ export interface RepresentationProps { } -export interface UnitsRepresentation<Props> { - renderObjects: ReadonlyArray<RenderObject>, - create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: Props) => Task<void>, - update: (props: RepresentationProps) => boolean, +export interface UnitsRepresentation<Props = {}> { + renderObjects: ReadonlyArray<RenderObject> + create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: Props) => Task<void> + update: (props: RepresentationProps) => Task<boolean> } -export interface StructureRepresentation<Props> { - renderObjects: ReadonlyArray<RenderObject>, - create: (structure: Structure, props?: Props) => Task<void>, - update: (elements: ElementSet, props: Props) => boolean +export interface StructureRepresentation<Props = {}> { + renderObjects: ReadonlyArray<RenderObject> + create: (structure: Structure, props?: Props) => Task<void> + update: (props: Props) => Task<void> +} + +interface GroupRepresentation<T> { + repr: UnitsRepresentation<T> + units: Unit[] + elementGroup: ElementGroup } export function StructureRepresentation<Props>(reprCtor: () => UnitsRepresentation<Props>): StructureRepresentation<Props> { const renderObjects: RenderObject[] = [] - const unitReprs: UnitsRepresentation<Props>[] = [] + const groupReprs: GroupRepresentation<Props>[] = [] return { renderObjects, create(structure: Structure, props: Props = {} as Props) { - return Task.create('StructureRepresentation', async ctx => { + return Task.create('StructureRepresentation.create', async ctx => { const { elements, units } = structure; const uniqueGroups = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>( ({ unit, group }) => ElementGroup.hashCode(group), @@ -57,25 +63,36 @@ export function StructureRepresentation<Props>(reprCtor: () => UnitsRepresentati // console.log({ uniqueGroups, uniqueTransformations }) - for (let i = 0, _i = uniqueGroups.groups.length; i < _i; i++) { + for (let i = 0, il = uniqueGroups.groups.length; i < il; i++) { const groupUnits: Unit[] = [] const group = uniqueGroups.groups[i] // console.log('group', i) - for (let j = 0, _j = group.length; j < _j; j++) { + for (let j = 0, jl = group.length; j < jl; j++) { groupUnits.push(units[group[j]]) } const elementGroup = ElementSet.groupFromUnitIndex(elements, group[0]) const repr = reprCtor() - unitReprs.push(repr) - await ctx.update({ message: 'Building units...', current: i, max: _i }); + groupReprs.push({ repr, units: groupUnits, elementGroup }) + await ctx.update({ message: 'Building units...', current: i, max: il }); await ctx.runChild(repr.create(groupUnits, elementGroup, props)); renderObjects.push(...repr.renderObjects) } }); }, - update(elements: ElementSet, props: RepresentationProps) { - // TODO check model.id, conformation.id, unit.id, elementGroup(.hashCode/.areEqual) - return false + update(props: Props) { + return Task.create('StructureRepresentation.update', async ctx => { + // TODO check model.id, conformation.id, unit.id, elementGroup(.hashCode/.areEqual) + renderObjects.length = 0 // clear + for (let i = 0, il = groupReprs.length; i < il; ++i) { + const groupRepr = groupReprs[i] + const { repr, units, elementGroup } = groupRepr + await ctx.update({ message: 'Updating units...', current: i, max: il }); + if (!await ctx.runChild(repr.update(props))) { + await ctx.runChild(repr.create(units, elementGroup, props)) + } + renderObjects.push(...repr.renderObjects) + } + }) } } } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts index 8735c23d8af2507a5934b051d0500dbce9eaab4f..b162a8a0a54563a2c19e58ccfc624d02ae24af88 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/src/mol-geo/representation/structure/point.ts @@ -6,7 +6,7 @@ import { ValueCell } from 'mol-util/value-cell' -import { createPointRenderObject, RenderObject } from 'mol-gl/scene' +import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/scene' import { OrderedSet } from 'mol-data/int' import { Unit, ElementGroup } from 'mol-model/structure'; @@ -40,50 +40,61 @@ export function createPointVertices(unit: Unit, elementGroup: ElementGroup) { export default function Point(): UnitsRepresentation<PointProps> { const renderObjects: RenderObject[] = [] + let points: PointRenderObject return { renderObjects, - create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: PointProps = {}) => Task.create('Spacefill', async ctx => { - const { colorTheme, sizeTheme } = { ...DefaultPointProps, ...props } - const elementCount = OrderedSet.size(elementGroup.elements) - const unitCount = units.length + create(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: PointProps = {}) { + return Task.create('Point.create', async ctx => { + renderObjects.length = 0 // clear - const vertexMap = VertexMap.create( - elementCount, - elementCount + 1, - fillSerial(new Uint32Array(elementCount)), - fillSerial(new Uint32Array(elementCount + 1)) - ) + const { colorTheme, sizeTheme } = { ...DefaultPointProps, ...props } + const elementCount = OrderedSet.size(elementGroup.elements) + const unitCount = units.length - await ctx.update('Computing point vertices'); - const vertices = createPointVertices(units[0], elementGroup) + const vertexMap = VertexMap.create( + elementCount, + elementCount + 1, + fillSerial(new Uint32Array(elementCount)), + fillSerial(new Uint32Array(elementCount + 1)) + ) - await ctx.update('Computing point transforms'); - const transforms = createTransforms(units) + await ctx.update('Computing point vertices'); + const vertices = createPointVertices(units[0], elementGroup) - await ctx.update('Computing point colors'); - const color = createColors(units, elementGroup, vertexMap, colorTheme) + await ctx.update('Computing point transforms'); + const transforms = createTransforms(units) - await ctx.update('Computing point sizes'); - const size = createSizes(units, elementGroup, vertexMap, sizeTheme) + await ctx.update('Computing point colors'); + const color = createColors(units, elementGroup, vertexMap, colorTheme) - const points = createPointRenderObject({ - objectId: 0, + await ctx.update('Computing point sizes'); + const size = createSizes(units, elementGroup, vertexMap, sizeTheme) - position: ValueCell.create(vertices), - id: ValueCell.create(fillSerial(new Float32Array(unitCount))), - size, - color, - transform: ValueCell.create(transforms), + points = createPointRenderObject({ + objectId: 0, - instanceCount: unitCount, - elementCount, - positionCount: vertices.length / 3, + position: ValueCell.create(vertices), + id: ValueCell.create(fillSerial(new Float32Array(unitCount))), + size, + color, + transform: ValueCell.create(transforms), - usePointSizeAttenuation: true + instanceCount: unitCount, + elementCount, + positionCount: vertices.length / 3, + + usePointSizeAttenuation: true + }) + renderObjects.push(points) + }) + }, + update(props: RepresentationProps) { + return Task.create('Point.update', async ctx => { + if (!points) return false + + return false }) - renderObjects.push(points) - }), - update: (props: RepresentationProps) => false + } } } diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 5778ecee60a5bf5548cf4209a22c71fd04faee21..05ac8cb750773bd098ac3f6c9cce59c2a04137af 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -6,7 +6,7 @@ import { ValueCell } from 'mol-util/value-cell' -import { RenderObject, createMeshRenderObject } from 'mol-gl/scene' +import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/scene' // import { createColorTexture } from 'mol-gl/util'; import { Vec3, Mat4 } from 'mol-math/linear-algebra' import { OrderedSet } from 'mol-data/int' @@ -59,44 +59,52 @@ function createSpacefillMesh(unit: Unit, elementGroup: ElementGroup, detail: num export default function Spacefill(): UnitsRepresentation<SpacefillProps> { const renderObjects: RenderObject[] = [] + let spheres: MeshRenderObject return { renderObjects, - create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: SpacefillProps = {}) => Task.create('Spacefill', async ctx => { - const { detail, colorTheme } = { ...DefaultSpacefillProps, ...props } + create(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: SpacefillProps = {}) { + return Task.create('Spacefill.create', async ctx => { + renderObjects.length = 0 // clear - const unitCount = units.length - const elementCount = OrderedSet.size(elementGroup.elements) + const { detail, colorTheme } = { ...DefaultSpacefillProps, ...props } - await ctx.update('Computing spacefill mesh'); - const mesh = await ctx.runChild(createSpacefillMesh(units[0], elementGroup, detail)) - // console.log(mesh) + await ctx.update('Computing spacefill mesh'); + const mesh = await ctx.runChild(createSpacefillMesh(units[0], elementGroup, detail)) + // console.log(mesh) - const vertexMap = VertexMap.fromMesh(mesh) + const vertexMap = VertexMap.fromMesh(mesh) - await ctx.update('Computing spacefill transforms'); - const transforms = createTransforms(units) + await ctx.update('Computing spacefill transforms'); + const transforms = createTransforms(units) - await ctx.update('Computing spacefill colors'); - const color = createColors(units, elementGroup, vertexMap, colorTheme) + await ctx.update('Computing spacefill colors'); + const color = createColors(units, elementGroup, vertexMap, colorTheme) - const spheres = createMeshRenderObject({ - objectId: 0, + spheres = createMeshRenderObject({ + objectId: 0, - position: mesh.vertexBuffer, - normal: mesh.normalBuffer as ValueCell<Float32Array>, - color: color, - id: mesh.idBuffer as ValueCell<Float32Array>, - transform: ValueCell.create(transforms), - index: mesh.indexBuffer, + position: mesh.vertexBuffer, + normal: mesh.normalBuffer as ValueCell<Float32Array>, + color: color, + id: mesh.idBuffer as ValueCell<Float32Array>, + transform: ValueCell.create(transforms), + index: mesh.indexBuffer, - instanceCount: unitCount, - indexCount: mesh.triangleCount, - elementCount: elementCount, - positionCount: mesh.vertexCount + instanceCount: units.length, + indexCount: mesh.triangleCount, + elementCount: OrderedSet.size(elementGroup.elements), + positionCount: mesh.vertexCount + }) + renderObjects.push(spheres) }) - renderObjects.push(spheres) - }), - update: (props: RepresentationProps) => false + }, + update(props: RepresentationProps) { + return Task.create('Spacefill.update', async ctx => { + if (!spheres) return false + + return false + }) + } } } diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 2d2afd56f84808ad6b2534b4b42086b8027b4cec..64b3f042993323905b89d245d4fb3188bf5cd5fa 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -78,13 +78,12 @@ namespace Renderer { baseContext(state => { regl.clear({ color: [0, 0, 0, 1] }) // TODO painters sort, filter visible, filter picking, visibility culling? - scene.forEach(r => { - r.draw() + scene.forEach((r, o) => { + if (o.visible) r.draw() }) }) } - // TODO animate, draw, requestDraw return { add: (o: RenderObject) => { scene.add(o) diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index ecc8444c1b9e53dd37aa4537e32cf9a0e1728adf..c26f442af1a6cde5fa54e02db527d0c3dbc595a0 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -16,15 +16,16 @@ function getNextId() { export type RenderData = { [k: string]: ValueCell<Helpers.TypedArray> } -export interface MeshRenderObject { id: number, type: 'mesh', props: MeshRenderable.Data } -export interface PointRenderObject { id: number, type: 'point', props: PointRenderable.Data } +export interface BaseRenderObject { id: number, type: string, props: {}, visible: boolean } +export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', props: MeshRenderable.Data } +export interface PointRenderObject extends BaseRenderObject { type: 'point', props: PointRenderable.Data } export type RenderObject = MeshRenderObject | PointRenderObject export function createMeshRenderObject(props: MeshRenderable.Data): MeshRenderObject { - return { id: getNextId(), type: 'mesh', props } + return { id: getNextId(), type: 'mesh', props, visible: true } } export function createPointRenderObject(props: PointRenderable.Data): PointRenderObject { - return { id: getNextId(), type: 'point', props } + return { id: getNextId(), type: 'point', props, visible: true } } export function createRenderable(regl: REGL.Regl, o: RenderObject) { @@ -38,39 +39,38 @@ interface Scene { add: (o: RenderObject) => void remove: (o: RenderObject) => void clear: () => void - forEach: (callbackFn: (value: Renderable) => void) => void + forEach: (callbackFn: (value: Renderable, key: RenderObject) => void) => void count: number } namespace Scene { export function create(regl: REGL.Regl): Scene { - const renderableList: Renderable[] = [] - const objectIdRenderableMap: { [k: number]: Renderable } = {} + const renderableMap = new Map<RenderObject, Renderable>() return { add: (o: RenderObject) => { - const renderable = createRenderable(regl, o) - renderableList.push(renderable) - objectIdRenderableMap[o.id] = renderable + if (!renderableMap.has(o)) { + renderableMap.set(o, createRenderable(regl, o)) + } else { + console.warn(`RenderObject with id '${o.id}' already present`) + } }, remove: (o: RenderObject) => { - if (o.id in objectIdRenderableMap) { - objectIdRenderableMap[o.id].dispose() - delete objectIdRenderableMap[o.id] + const renderable = renderableMap.get(o) + if (renderable) { + renderable.dispose() + renderableMap.delete(o) } }, clear: () => { - for (const id in objectIdRenderableMap) { - objectIdRenderableMap[id].dispose() - delete objectIdRenderableMap[id] - } - renderableList.length = 0 + renderableMap.forEach(renderable => renderable.dispose()) + renderableMap.clear() }, - forEach: (callbackFn: (value: Renderable) => void) => { - renderableList.forEach(callbackFn) + forEach: (callbackFn: (value: Renderable, key: RenderObject) => void) => { + renderableMap.forEach(callbackFn) }, get count() { - return renderableList.length + return renderableMap.size } } } diff --git a/src/mol-util/set.ts b/src/mol-util/set.ts new file mode 100644 index 0000000000000000000000000000000000000000..555b13d7a7771b87112e0288080f4a304d4cd3d9 --- /dev/null +++ b/src/mol-util/set.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +// TODO remove Array.from workaround when targeting ES6 + +/** Test if set a contains all elements of set b. */ +export function isSuperset<T>(setA: Set<T>, setB: Set<T>) { + for (const elm of Array.from(setB)) { + if (!setA.has(elm)) return false; + } + return true; +} + +/** Create set containing elements of both set a and set b. */ +export function union<T>(setA: Set<T>, setB: Set<T>) { + const union = new Set(setA); + for (const elem of Array.from(setB)) union.add(elem); + return union; +} + +/** Create set containing elements of set a that are also in set b. */ +export function intersection<T>(setA: Set<T>, setB: Set<T>) { + const intersection = new Set(); + for (const elem of Array.from(setB)) { + if (setA.has(elem)) intersection.add(elem); + } + return intersection; +} + +/** Create set containing elements of set a that are not in set b. */ +export function difference<T>(setA: Set<T>, setB: Set<T>) { + const difference = new Set(setA); + for (const elem of Array.from(setB)) difference.delete(elem); + return difference; +} \ No newline at end of file diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 667a0d0353bdb63da9c2b5a131d65f0dae95c5b6..faf022963a3e741218c189f6f59b7a1fc94131a6 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -6,8 +6,10 @@ import { Vec3 } from 'mol-math/linear-algebra' import InputObserver from 'mol-util/input/input-observer' +import * as SetUtils from 'mol-util/set' import Renderer, { RendererStats } from 'mol-gl/renderer' import { RenderObject } from 'mol-gl/scene' +import { StructureRepresentation } from 'mol-geo/representation/structure'; import TrackballControls from './controls/trackball' import { Viewport } from './camera/util' @@ -15,11 +17,14 @@ import { PerspectiveCamera } from './camera/perspective' import { resizeCanvas } from './util'; interface Viewer { - add: (o: RenderObject) => void - remove: (o: RenderObject) => void + hide: (repr: StructureRepresentation) => void + show: (repr: StructureRepresentation) => void + + add: (repr: StructureRepresentation) => void + remove: (repr: StructureRepresentation) => void clear: () => void - draw: () => void + draw: () => void requestDraw: () => void animate: () => void @@ -42,6 +47,8 @@ function getWebGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLCon namespace Viewer { export function create(canvas: HTMLCanvasElement, container: Element): Viewer { + const reprMap = new Map<StructureRepresentation, Set<RenderObject>>() + const input = InputObserver.create(canvas) input.resize.subscribe(handleResize) @@ -82,13 +89,33 @@ namespace Viewer { handleResize() return { - add: (o: RenderObject) => { - renderer.add(o) + hide: (repr: StructureRepresentation) => { + const renderObjectSet = reprMap.get(repr) + if (renderObjectSet) renderObjectSet.forEach(o => o.visible = false) + }, + show: (repr: StructureRepresentation) => { + const renderObjectSet = reprMap.get(repr) + if (renderObjectSet) renderObjectSet.forEach(o => o.visible = true) + }, + + add: (repr: StructureRepresentation) => { + const oldRO = reprMap.get(repr) + const newRO = new Set<RenderObject>() + repr.renderObjects.forEach(o => newRO.add(o)) + if (oldRO) { + SetUtils.difference(newRO, oldRO).forEach(o => renderer.add(o)) + SetUtils.difference(oldRO, newRO).forEach(o => renderer.remove(o)) + } else { + repr.renderObjects.forEach(o => renderer.add(o)) + } + reprMap.set(repr, newRO) }, - remove: (o: RenderObject) => { - renderer.remove(o) + remove: (repr: StructureRepresentation) => { + const renderObjectSet = reprMap.get(repr) + if (renderObjectSet) renderObjectSet.forEach(o => renderer.remove(o)) }, clear: () => { + reprMap.clear() renderer.clear() },