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