From 8d570b190f31a5594069e204cc05e55af4f31860 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Tue, 5 Jun 2018 16:37:42 +0200 Subject: [PATCH] wip, bond repr --- src/mol-app/ui/entity/tree.tsx | 7 +- src/mol-app/ui/transform/bond.tsx | 194 ++++++++++++++++++ src/mol-app/ui/transform/list.tsx | 3 + src/mol-geo/representation/structure/bond.ts | 179 ++++++++++++++++ .../representation/structure/spacefill.ts | 2 +- src/mol-view/state/entity.ts | 8 + src/mol-view/state/transform.ts | 36 +++- 7 files changed, 415 insertions(+), 14 deletions(-) create mode 100644 src/mol-app/ui/transform/bond.tsx create mode 100644 src/mol-geo/representation/structure/bond.ts diff --git a/src/mol-app/ui/entity/tree.tsx b/src/mol-app/ui/entity/tree.tsx index cc889f7ff..66eba7892 100644 --- a/src/mol-app/ui/entity/tree.tsx +++ b/src/mol-app/ui/entity/tree.tsx @@ -13,7 +13,7 @@ import { View } from '../view'; import { EntityTreeController } from '../../controller/entity/tree'; import { Controller } from '../../controller/controller'; import { AnyEntity, RootEntity } from 'mol-view/state/entity'; -import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter } from 'mol-view/state/transform'; +import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter, StructureToBond, BondUpdate } from 'mol-view/state/transform'; function getTransforms(entity: AnyEntity): AnyTransform[] { const transforms: AnyTransform[] = [] @@ -40,11 +40,14 @@ function getTransforms(entity: AnyEntity): AnyTransform[] { transforms.push(ModelToStructure) break; case 'structure': - transforms.push(StructureToSpacefill, StructureCenter) + transforms.push(StructureToSpacefill, StructureToBond, StructureCenter) break; case 'spacefill': transforms.push(SpacefillUpdate) break; + case 'bond': + transforms.push(BondUpdate) + break; } return transforms } diff --git a/src/mol-app/ui/transform/bond.tsx b/src/mol-app/ui/transform/bond.tsx new file mode 100644 index 000000000..cc87a6dc8 --- /dev/null +++ b/src/mol-app/ui/transform/bond.tsx @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { View } from '../view'; +import { Controller } from '../../controller/controller'; +import { Toggle } from '../controls/common'; +import { BondEntity } from 'mol-view/state/entity'; +import { BondUpdate } from 'mol-view/state/transform' +import { StateContext } from 'mol-view/state/context'; +import { ColorTheme } from 'mol-geo/theme'; +import { Color, ColorNames } from 'mol-util/color'; +import { Slider } from '../controls/slider'; + +export const ColorThemeInfo = { + 'atom-index': {}, + 'chain-id': {}, + 'element-symbol': {}, + 'instance-index': {}, + 'uniform': {} +} +export type ColorThemeInfo = keyof typeof ColorThemeInfo + +interface BondState { + doubleSided: boolean + flipSided: boolean + flatShaded: boolean + colorTheme: ColorTheme + colorValue: Color + visible: boolean + alpha: number + depthMask: boolean +} + +export class Bond extends View<Controller<any>, BondState, { transform: BondUpdate, entity: BondEntity, ctx: StateContext }> { + state = { + doubleSided: true, + flipSided: false, + flatShaded: false, + colorTheme: { name: 'element-symbol' } as ColorTheme, + colorValue: 0x000000, + visible: true, + alpha: 1, + depthMask: true + } + + update(state?: Partial<BondState>) { + const { transform, entity, ctx } = this.props + const newState = { ...this.state, ...state } + this.setState(newState) + transform.apply(ctx, entity, newState) + } + + render() { + const { transform } = this.props + + const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => { + return <option key={name} value={name}>{name}</option> + }) + + const colorValueOptions = Object.keys(ColorNames).map((name, idx) => { + return <option key={name} value={(ColorNames as any)[name]}>{name}</option> + }) + + return <div className='molstar-transformer-wrapper'> + <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'> + <div className='molstar-panel-header'> + <button + className='molstar-btn molstar-btn-link molstar-panel-expander' + onClick={() => this.update()} + > + <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span> + </button> + </div> + <div className='molstar-panel-body'> + <div> + <div className='molstar-control-row molstar-options-group'> + <span>Color theme</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorTheme.name} + onChange={(e) => { + const colorThemeName = e.target.value as ColorThemeInfo + if (colorThemeName === 'uniform') { + this.update({ + colorTheme: { + name: colorThemeName, + value: this.state.colorValue + } + }) + } else { + this.update({ + colorTheme: { name: colorThemeName } + }) + } + }} + > + {colorThemeOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <span>Color value</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorValue} + onChange={(e) => { + const colorValue = parseInt(e.target.value) + this.update({ + colorTheme: { + name: 'uniform', + value: colorValue + }, + colorValue + }) + }} + > + {colorValueOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.visible} + label='Visibility' + onChange={value => this.update({ visible: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.depthMask} + label='Depth write' + onChange={value => this.update({ depthMask: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.doubleSided} + label='Double sided' + onChange={value => this.update({ doubleSided: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.flipSided} + label='Flip sided' + onChange={value => this.update({ flipSided: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.flatShaded} + label='Flat shaded' + onChange={value => this.update({ flatShaded: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.alpha} + label='Opacity' + min={0} + max={1} + step={0.01} + callOnChangeWhileSliding={true} + onChange={value => this.update({ alpha: value })} + /> + </div> + </div> + </div> + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/transform/list.tsx b/src/mol-app/ui/transform/list.tsx index dd3840ac6..8fa592ee0 100644 --- a/src/mol-app/ui/transform/list.tsx +++ b/src/mol-app/ui/transform/list.tsx @@ -14,6 +14,7 @@ import { Controller } from '../../controller/controller'; import { TransformListController } from '../../controller/transform/list'; import { AnyTransform } from 'mol-view/state/transform'; import { Spacefill } from './spacefill'; +import { Bond } from './bond'; import { AnyEntity } from 'mol-view/state/entity'; import { FileLoader } from './file-loader'; import { ModelToStructure } from './model'; @@ -29,6 +30,8 @@ function getTransformComponent(controller: TransformListController, entity: AnyE return <StructureCenter controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></StructureCenter> case 'spacefill-update': return <Spacefill controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Spacefill> + case 'bond-update': + return <Bond controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Bond> } return <Transform controller={controller} entity={entity} transform={transform}></Transform> } diff --git a/src/mol-geo/representation/structure/bond.ts b/src/mol-geo/representation/structure/bond.ts new file mode 100644 index 000000000..06addb9a0 --- /dev/null +++ b/src/mol-geo/representation/structure/bond.ts @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { ValueCell } from 'mol-util/value-cell' + +import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' +import { Unit, Element } from 'mol-model/structure'; +import { UnitsRepresentation, DefaultStructureProps } from './index'; +import { Task } from 'mol-task' +import { createTransforms, createEmptyFlags } from './utils'; +import { fillSerial } from 'mol-gl/renderable/util'; +import { RenderableState, MeshValues } from 'mol-gl/renderable'; +import { getMeshData } from '../../util/mesh-data'; +import { Mesh } from '../../shape/mesh'; +import { PickingId } from '../../util/picking'; +import { MeshBuilder } from '../../shape/mesh-builder'; +import { Vec3, Mat4 } from 'mol-math/linear-algebra'; +import { createUniformColor } from '../../util/color-data'; +import { defaults } from 'mol-util'; + +function createBondMesh(unit: Unit, mesh?: Mesh) { + return Task.create('Cylinder mesh', async ctx => { + if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) + + const elements = unit.elements; + const { count, offset, neighbor } = unit.bonds; + + if (!count) return Mesh.createEmpty(mesh) + + // TODO calculate properly + const vertexCount = 32 * count + const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh) + + const va = Vec3.zero() + const vb = Vec3.zero() + const vt = Vec3.zero() + const m = Mat4.identity() + + const { x, y, z } = unit.conformation + const l = Element.Location() + l.unit = unit + + for (let j = 0; j < offset.length - 1; ++j) { + const start = offset[j] + const end = offset[j + 1] + + if (end <= start) continue + + const aI = elements[j] + va[0] = x(aI) + va[1] = y(aI) + va[2] = z(aI) + for (let _bI = start; _bI < end; ++_bI) { + const bI = elements[neighbor[_bI]] + if (bI > aI) continue + + vb[0] = x(bI) + vb[1] = y(bI) + vb[2] = z(bI) + + Vec3.scale(vt, Vec3.add(vt, va, vb), 0.5) + Vec3.makeRotation(m, Vec3.create(0, 1, 0), Vec3.sub(vb, vb, va)) + Mat4.setTranslation(m, vt) + + meshBuilder.setId(j) + meshBuilder.addCylinder(m, { radiusTop: 0.2, radiusBottom: 0.2 }) + } + + if (j % 10000 === 0 && ctx.shouldUpdate) { + await ctx.update({ message: 'Cylinder mesh', current: j, max: count }); + } + } + + return meshBuilder.getMesh() + }) +} + +export const DefaultBondProps = { + ...DefaultStructureProps, + flipSided: false, + flatShaded: false, +} +export type BondProps = Partial<typeof DefaultBondProps> + +export default function Bond(): UnitsRepresentation<BondProps> { + const renderObjects: RenderObject[] = [] + let cylinders: MeshRenderObject + let currentProps: typeof DefaultBondProps + let mesh: Mesh + // let currentGroup: Unit.SymmetryGroup + // let vertexMap: VertexMap + + return { + renderObjects, + create(group: Unit.SymmetryGroup, props: BondProps = {}) { + currentProps = Object.assign({}, DefaultBondProps, props) + + return Task.create('Bond.create', async ctx => { + renderObjects.length = 0 // clear + // currentGroup = group + + mesh = await createBondMesh(group.units[0]).runAsChild(ctx, 'Computing bond mesh') + // console.log(mesh) + // vertexMap = VertexMap.fromMesh(mesh) + + await ctx.update('Computing bond transforms'); + const transforms = createTransforms(group) + + await ctx.update('Computing bond colors'); + const color = createUniformColor({ value: 0xFF0000 }) + + await ctx.update('Computing bond flags'); + const flag = createEmptyFlags() + + const instanceCount = group.units.length + + const values: MeshValues = { + ...getMeshData(mesh), + aTransform: transforms, + aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))), + ...color, + ...flag, + + uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), + uInstanceCount: ValueCell.create(instanceCount), + uElementCount: ValueCell.create(group.elements.length), + + elements: mesh.indexBuffer, + + drawCount: ValueCell.create(mesh.triangleCount * 3), + instanceCount: ValueCell.create(instanceCount), + + dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)), + dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)), + dFlipSided: ValueCell.create(defaults(props.flipSided, false)), + } + const state: RenderableState = { + depthMask: defaults(props.depthMask, true), + visible: defaults(props.visible, true) + } + + cylinders = createMeshRenderObject(values, state) + renderObjects.push(cylinders) + }) + }, + update(props: BondProps) { + const newProps = Object.assign({}, currentProps, props) + + return Task.create('Bond.update', async ctx => { + if (!cylinders) return false + // TODO + + ValueCell.updateIfChanged(cylinders.values.uAlpha, newProps.alpha) + ValueCell.updateIfChanged(cylinders.values.dDoubleSided, newProps.doubleSided) + ValueCell.updateIfChanged(cylinders.values.dFlipSided, newProps.flipSided) + ValueCell.updateIfChanged(cylinders.values.dFlatShaded, newProps.flatShaded) + + cylinders.state.visible = newProps.visible + cylinders.state.depthMask = newProps.depthMask + + return true + }) + }, + getLocation(pickingId: PickingId) { + // const { objectId, instanceId, elementId } = pickingId + // if (cylinders.id === objectId) { + // const l = Element.Location() + // l.unit = currentGroup.units[instanceId] + // l.element = currentGroup.elements[elementId] + // return l + // } + return null + } + } +} diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index ebfbe71a1..abc4e389c 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -30,7 +30,7 @@ function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) { console.warn('Unsupported unit type') return Task.constant('Empty mesh', Mesh.createEmpty(mesh)) } - return createSphereMesh(unit, radius, detail, mesh) + return createSphereMesh(unit, (l) => radius(l) * 0.3, detail, mesh) } export const DefaultSpacefillProps = { diff --git a/src/mol-view/state/entity.ts b/src/mol-view/state/entity.ts index 2b28f71d3..a35ec6614 100644 --- a/src/mol-view/state/entity.ts +++ b/src/mol-view/state/entity.ts @@ -13,6 +13,7 @@ import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'; import { Model, Structure } from 'mol-model/structure'; import { StructureRepresentation } from 'mol-geo/representation/structure'; import { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; +import { BondProps } from 'mol-geo/representation/structure/bond'; const getNextId = idFactory(1) @@ -119,4 +120,11 @@ export namespace SpacefillEntity { export function ofRepr(ctx: StateContext, repr: StructureRepresentation<SpacefillProps>): SpacefillEntity { return StateEntity.create(ctx, 'spacefill', repr ) } +} + +export type BondEntity = StateEntity<StructureRepresentation<BondProps>, 'bond'> +export namespace BondEntity { + export function ofRepr(ctx: StateContext, repr: StructureRepresentation<BondProps>): BondEntity { + return StateEntity.create(ctx, 'bond', repr ) + } } \ No newline at end of file diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts index f7e16db18..6ed6de1d4 100644 --- a/src/mol-view/state/transform.ts +++ b/src/mol-view/state/transform.ts @@ -5,13 +5,14 @@ */ import CIF from 'mol-io/reader/cif' -import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity } from './entity'; +import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity, BondEntity } from './entity'; import { Model, Structure } from 'mol-model/structure'; import { StateContext } from './context'; import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; import { StructureRepresentation } from 'mol-geo/representation/structure'; import StructureSymmetry from 'mol-model/structure/structure/symmetry'; +import Bond, { BondProps } from 'mol-geo/representation/structure/bond'; type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O> @@ -98,30 +99,42 @@ export const StructureToSpacefill: StructureToSpacefill = StateTransform.create( ctx.viewer.add(spacefillRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) - // ctx.viewer.input.drag.subscribe(async () => { - // console.log('drag') - // console.time('spacefill update') - // await spacefillRepr.update(props).run(ctx.log) - // console.timeEnd('spacefill update') - // ctx.viewer.add(spacefillRepr) - // ctx.viewer.update() - // ctx.viewer.requestDraw() - // }) return SpacefillEntity.ofRepr(ctx, spacefillRepr) }) +export type StructureToBond = StateTransform<StructureEntity, BondEntity, BondProps> + export const StructureToBond: StructureToBond = StateTransform.create('structure', 'bond', 'structure-to-bond', + async function (ctx: StateContext, structureEntity: StructureEntity, props: BondProps = {}) { + const bondRepr = StructureRepresentation(Bond) + await bondRepr.create(structureEntity.value, props).run(ctx.log) + ctx.viewer.add(bondRepr) + ctx.viewer.requestDraw() + console.log('stats', ctx.viewer.stats) + return BondEntity.ofRepr(ctx, bondRepr) + }) + export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, SpacefillProps> export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'null', 'spacefill-update', async function (ctx: StateContext, spacefillEntity: SpacefillEntity, props: SpacefillProps = {}) { const spacefillRepr = spacefillEntity.value await spacefillRepr.update(props).run(ctx.log) ctx.viewer.add(spacefillRepr) - // ctx.viewer.update() ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) return NullEntity }) +export type BondUpdate = StateTransform<BondEntity, NullEntity, BondProps> + export const BondUpdate: BondUpdate = StateTransform.create('bond', 'null', 'bond-update', + async function (ctx: StateContext, bondEntity: BondEntity, props: BondProps = {}) { + const bondRepr = bondEntity.value + await bondRepr.update(props).run(ctx.log) + ctx.viewer.add(bondRepr) + ctx.viewer.requestDraw() + console.log('stats', ctx.viewer.stats) + return NullEntity + }) + // composed export type MmcifUrlToModel = StateTransform<UrlEntity, ModelEntity, {}> @@ -150,6 +163,7 @@ export type ModelToSpacefill = StateTransform<ModelEntity, SpacefillEntity, Spac export const ModelToSpacefill: ModelToSpacefill = StateTransform.create('model', 'spacefill', 'model-to-spacefill', async function (ctx: StateContext, modelEntity: ModelEntity, props: SpacefillProps = {}) { const structureEntity = await ModelToStructure.apply(ctx, modelEntity) + StructureToBond.apply(ctx, structureEntity, props) return StructureToSpacefill.apply(ctx, structureEntity, props) }) -- GitLab