Browse Source

wip, support highlight and select state

Alexander Rose 6 years ago
parent
commit
783d600bca

+ 2 - 0
src/mol-geo/representation/index.ts

@@ -8,6 +8,7 @@ import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object'
 import { PickingId } from '../util/picking';
 import { Loci } from 'mol-model/loci';
+import { FlagAction } from '../util/flag-data';
 
 export interface RepresentationProps {}
 
@@ -16,4 +17,5 @@ export interface Representation<D, P extends RepresentationProps = {}> {
     create: (data: D, props?: P) => Task<void>
     update: (props: P) => Task<void>
     getLoci: (pickingId: PickingId) => Loci | null
+    applyFlags: (loci: Loci, action: FlagAction) => void
 }

+ 7 - 2
src/mol-geo/representation/structure/bond.ts

@@ -11,7 +11,7 @@ import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/r
 import { Unit, Element } from 'mol-model/structure';
 import { UnitsRepresentation, DefaultStructureProps } from './index';
 import { Task } from 'mol-task'
-import { createTransforms, createEmptyFlags } from './utils';
+import { createTransforms } from './utils';
 import { fillSerial } from 'mol-gl/renderable/util';
 import { RenderableState, MeshValues } from 'mol-gl/renderable';
 import { getMeshData } from '../../util/mesh-data';
@@ -21,7 +21,8 @@ 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';
-import { SortedArray } from 'mol-data/int';
+import { Loci } from 'mol-model/loci';
+import { FlagAction, createEmptyFlags } from '../../util/flag-data';
 
 function createBondMesh(unit: Unit, mesh?: Mesh) {
     return Task.create('Cylinder mesh', async ctx => {
@@ -174,6 +175,10 @@ export default function Bond(): UnitsRepresentation<BondProps> {
             //     return Element.Loci([{ unit, elements }])
             // }
             return null
+        },
+        applyFlags(loci: Loci, action: FlagAction) {
+            currentGroup
+            // TODO
         }
     }
 }

+ 10 - 9
src/mol-geo/representation/structure/index.ts

@@ -12,20 +12,17 @@ import { Representation, RepresentationProps } from '..';
 import { ColorTheme } from '../../theme';
 import { PickingId } from '../../util/picking';
 import { Loci } from 'mol-model/loci';
+import { FlagAction } from '../../util/flag-data';
 
 export interface UnitsRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
     create: (group: Unit.SymmetryGroup, props: P) => Task<void>
     update: (props: P) => Task<boolean>
     getLoci: (pickingId: PickingId) => Loci | null
+    applyFlags: (loci: Loci, action: FlagAction) => void
 }
 
-export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> {
-    renderObjects: ReadonlyArray<RenderObject>
-    create: (structure: Structure, props?: P) => Task<void>
-    update: (props: P) => Task<void>
-    getLoci: (pickingId: PickingId) => Loci | null
-}
+export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
 
 interface GroupRepresentation<T> {
     repr: UnitsRepresentation<T>
@@ -37,8 +34,7 @@ export const DefaultStructureProps = {
     alpha: 1,
     visible: true,
     doubleSided: false,
-    depthMask: true,
-    hoverSelection: { objectId: -1, instanceId: -1, elementId: -1 } as PickingId
+    depthMask: true
 }
 export type StructureProps = Partial<typeof DefaultStructureProps>
 
@@ -91,6 +87,11 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () =
                 }
             })
         },
-        getLoci
+        getLoci,
+        applyFlags(loci: Loci, action: FlagAction) {
+            for (let i = 0, il = groupReprs.length; i < il; ++i) {
+                groupReprs[i].repr.applyFlags(loci, action)
+            }
+        }
     }
 }

+ 10 - 5
src/mol-geo/representation/structure/point.ts

@@ -14,11 +14,13 @@ import { fillSerial } from 'mol-gl/renderable/util';
 import { UnitsRepresentation, DefaultStructureProps } from './index';
 import VertexMap from '../../shape/vertex-map';
 import { SizeTheme } from '../../theme';
-import { createTransforms, createColors, createSizes, createFlags } from './utils';
+import { createTransforms, createColors, createSizes, applyElementFlags } from './utils';
 import { deepEqual, defaults } from 'mol-util';
 import { SortedArray } from 'mol-data/int';
 import { RenderableState, PointValues } from 'mol-gl/renderable';
 import { PickingId } from '../../util/picking';
+import { Loci } from 'mol-model/loci';
+import { FlagAction, createFlags } from '../../util/flag-data';
 
 export const DefaultPointProps = {
     ...DefaultStructureProps,
@@ -66,7 +68,7 @@ export default function Point(): UnitsRepresentation<PointProps> {
                 _units = group.units
                 _elements = group.elements;
 
-                const { colorTheme, sizeTheme, hoverSelection } = currentProps
+                const { colorTheme, sizeTheme } = currentProps
                 const elementCount = _elements.length
 
                 const vertexMap = VertexMap.create(
@@ -89,7 +91,7 @@ export default function Point(): UnitsRepresentation<PointProps> {
                 const size = createSizes(group, vertexMap, sizeTheme)
 
                 await ctx.update('Computing spacefill flags');
-                const flag = createFlags(group, hoverSelection.instanceId, hoverSelection.elementId)
+                const flag = createFlags(group)
 
                 const instanceCount = group.units.length
 
@@ -159,10 +161,13 @@ export default function Point(): UnitsRepresentation<PointProps> {
             const { objectId, instanceId, elementId } = pickingId
             if (points.id === objectId) {
                 const unit = currentGroup.units[instanceId]
-                const elements = SortedArray.ofSingleton(currentGroup.elements[elementId])
-                return Element.Loci([{ unit, elements }])
+                const indices = SortedArray.ofSingleton(elementId)
+                return Element.Loci([{ unit, indices }])
             }
             return null
+        },
+        applyFlags(loci: Loci, action: FlagAction) {
+            applyElementFlags(points.values.tFlag, currentGroup, loci, action)
         }
     }
 }

+ 11 - 15
src/mol-geo/representation/structure/spacefill.ts

@@ -11,7 +11,7 @@ import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/r
 import { Unit, Element, Queries } from 'mol-model/structure';
 import { UnitsRepresentation, DefaultStructureProps } from './index';
 import { Task } from 'mol-task'
-import { createTransforms, createColors, createFlags, createEmptyFlags, createSphereMesh } from './utils';
+import { createTransforms, createColors, createSphereMesh, applyElementFlags } from './utils';
 import VertexMap from '../../shape/vertex-map';
 import { deepEqual, defaults } from 'mol-util';
 import { fillSerial } from 'mol-gl/renderable/util';
@@ -20,6 +20,8 @@ import { getMeshData } from '../../util/mesh-data';
 import { Mesh } from '../../shape/mesh';
 import { PickingId } from '../../util/picking';
 import { SortedArray } from 'mol-data/int';
+import { createFlags, FlagAction } from '../../util/flag-data';
+import { Loci } from 'mol-model/loci';
 
 function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) {
     let radius: Element.Property<number>
@@ -31,7 +33,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, (l) => radius(l) * 0.3, detail, mesh)
+    return createSphereMesh(unit, (l) => radius(l) * 1.0, detail, mesh)
 }
 
 export const DefaultSpacefillProps = {
@@ -59,7 +61,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                 renderObjects.length = 0 // clear
                 currentGroup = group
 
-                const { detail, colorTheme, hoverSelection } = { ...DefaultSpacefillProps, ...props }
+                const { detail, colorTheme } = { ...DefaultSpacefillProps, ...props }
 
                 mesh = await createSpacefillMesh(group.units[0], detail).runAsChild(ctx, 'Computing spacefill mesh')
                 // console.log(mesh)
@@ -72,7 +74,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                 const color = createColors(group, vertexMap, colorTheme)
 
                 await ctx.update('Computing spacefill flags');
-                const flag = createFlags(group, hoverSelection.instanceId, hoverSelection.elementId)
+                const flag = createFlags(group)
 
                 const instanceCount = group.units.length
 
@@ -130,15 +132,6 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                     createColors(currentGroup, vertexMap, newProps.colorTheme, spheres.values)
                 }
 
-                if (newProps.hoverSelection !== currentProps.hoverSelection) {
-                    await ctx.update('Computing spacefill flags');
-                    if (newProps.hoverSelection.objectId === spheres.id) {
-                        createFlags(currentGroup, newProps.hoverSelection.instanceId, newProps.hoverSelection.elementId, spheres.values)
-                    } else {
-                        createEmptyFlags(spheres.values)
-                    }
-                }
-
                 ValueCell.updateIfChanged(spheres.values.uAlpha, newProps.alpha)
                 ValueCell.updateIfChanged(spheres.values.dDoubleSided, newProps.doubleSided)
                 ValueCell.updateIfChanged(spheres.values.dFlipSided, newProps.flipSided)
@@ -155,10 +148,13 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
             const { objectId, instanceId, elementId } = pickingId
             if (spheres.id === objectId) {
                 const unit = currentGroup.units[instanceId]
-                const elements = SortedArray.ofSingleton(currentGroup.elements[elementId])
-                return Element.Loci([{ unit, elements }])
+                const indices = SortedArray.ofSingleton(elementId);
+                return Element.Loci([{ unit, indices }])
             }
             return null
+        },
+        applyFlags(loci: Loci, action: FlagAction) {
+            applyElementFlags(spheres.values.tFlag, currentGroup, loci, action)
         }
     }
 }

+ 33 - 46
src/mol-geo/representation/structure/utils.ts

@@ -6,7 +6,7 @@
  */
 
 import { Unit, Element } from 'mol-model/structure';
-import { Mat4, Vec2, Vec3 } from 'mol-math/linear-algebra'
+import { Mat4, Vec3 } from 'mol-math/linear-algebra'
 
 import { createUniformColor, ColorData } from '../../util/color-data';
 import { createUniformSize } from '../../util/size-data';
@@ -15,11 +15,13 @@ import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color';
 import { ValueCell } from 'mol-util';
-import { TextureImage, createTextureImage } from 'mol-gl/renderable/util';
 import { Mesh } from '../../shape/mesh';
 import { Task } from 'mol-task';
 import { icosahedronVertexCount } from '../../primitive/icosahedron';
 import { MeshBuilder } from '../../shape/mesh-builder';
+import { TextureImage } from 'mol-gl/renderable/util';
+import { applyFlagAction, FlagAction } from '../../util/flag-data';
+import { Loci, isEveryLoci } from 'mol-model/loci';
 
 export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) {
     const unitCount = units.length
@@ -55,50 +57,6 @@ export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, pro
     }
 }
 
-export type FlagData = {
-    tFlag: ValueCell<TextureImage>
-    uFlagTexSize: ValueCell<Vec2>
-}
-
-export function createFlags(group: Unit.SymmetryGroup, instanceId: number, elementId: number, flagData?: FlagData): FlagData {
-    const instanceCount = group.units.length
-    const elementCount = group.elements.length
-    const count = instanceCount * elementCount
-    const flags = flagData && flagData.tFlag.ref.value.array.length >= count ? flagData.tFlag.ref.value : createTextureImage(count, 1)
-    let flagOffset = 0
-    for (let i = 0; i < instanceCount; i++) {
-        for (let j = 0, jl = elementCount; j < jl; ++j) {
-            flags.array[flagOffset] = (i === instanceId && j === elementId) ? 255 : 0
-            flagOffset += 1
-        }
-    }
-    // console.log(flags, instanceCount, elementCount)
-    if (flagData) {
-        ValueCell.update(flagData.tFlag, flags)
-        ValueCell.update(flagData.uFlagTexSize, Vec2.create(flags.width, flags.height))
-        return flagData
-    } else {
-        return {
-            tFlag: ValueCell.create(flags),
-            uFlagTexSize: ValueCell.create(Vec2.create(flags.width, flags.height)),
-        }
-    }
-}
-
-const emptyFlagTexture = { array: new Uint8Array(1), width: 1, height: 1 }
-export function createEmptyFlags(flagData?: FlagData) {
-    if (flagData) {
-        ValueCell.update(flagData.tFlag, emptyFlagTexture)
-        ValueCell.update(flagData.uFlagTexSize, Vec2.create(1, 1))
-        return flagData
-    } else {
-        return {
-            tFlag: ValueCell.create(emptyFlagTexture),
-            uFlagTexSize: ValueCell.create(Vec2.create(1, 1)),
-        }
-    }
-}
-
 export function createSphereMesh(unit: Unit, radius: Element.Property<number>, detail: number, mesh?: Mesh) {
     return Task.create('Sphere mesh', async ctx => {
         const { elements } = unit;
@@ -131,3 +89,32 @@ export function createSphereMesh(unit: Unit, radius: Element.Property<number>, d
         return meshBuilder.getMesh()
     })
 }
+
+
+export function applyElementFlags(tFlag: ValueCell<TextureImage>, group: Unit.SymmetryGroup, loci: Loci, action: FlagAction) {
+    let changed = false
+    const elementCount = group.elements.length
+    const instanceCount = group.units.length
+    const array = tFlag.ref.value.array
+    if (isEveryLoci(loci)) {
+        applyFlagAction(array, 0, elementCount * instanceCount, action)
+        changed = true
+    } else if (Element.isLoci(loci)) {
+        for (const e of loci.elements) {
+            const unitIdx = Unit.findUnitById(e.unit.id, group.units)
+            if (unitIdx !== -1) {
+                for (let i = 0, il = e.indices.length; i < il; ++i) {
+                    const idx = unitIdx * elementCount + e.indices[i]
+                    if (applyFlagAction(array, idx, idx + 1, action) && !changed) {
+                        changed = true
+                    }
+                }
+            }
+        }
+    } else {
+        return
+    }
+    if (changed) {
+        ValueCell.update(tFlag, tFlag.ref.value)
+    }
+}

+ 8 - 8
src/mol-geo/representation/volume/index.ts

@@ -8,23 +8,20 @@ import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
 import { RepresentationProps, Representation } from '..';
 import { VolumeData } from 'mol-model/volume';
-import { PickingId, PickingInfo } from '../../util/picking';
+import { PickingId } from '../../util/picking';
 import { Loci } from 'mol-model/loci';
+import { FlagAction } from '../../util/flag-data';
 
 export interface VolumeElementRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
     create: (volumeData: VolumeData, props: P) => Task<void>
     update: (props: P) => Task<boolean>
-    getLabel: (pickingId: PickingId) => PickingInfo | null
-}
-
-export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> {
-    renderObjects: ReadonlyArray<RenderObject>
-    create: (volumeData: VolumeData, props?: P) => Task<void>
-    update: (props: P) => Task<void>
     getLoci: (pickingId: PickingId) => Loci | null
+    applyFlags: (loci: Loci, action: FlagAction) => void
 }
 
+export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { }
+
 export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentation<P>): VolumeRepresentation<P> {
     const renderObjects: RenderObject[] = []
 
@@ -43,6 +40,9 @@ export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentat
         getLoci(pickingId: PickingId) {
             // TODO
             return null
+        },
+        applyFlags(loci: Loci, action: FlagAction) {
+            // TODO
         }
     }
 }

+ 7 - 2
src/mol-geo/representation/volume/surface.ts

@@ -18,7 +18,8 @@ import { createUniformColor } from '../../util/color-data';
 import { getMeshData } from '../../util/mesh-data';
 import { RenderableState, MeshValues } from 'mol-gl/renderable';
 import { PickingId } from '../../util/picking';
-import { createEmptyFlags } from '../structure/utils';
+import { createEmptyFlags, FlagAction } from '../../util/flag-data';
+import { Loci } from 'mol-model/loci';
 
 export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) {
     return Task.create<Mesh>('Volume Surface', async ctx => {
@@ -104,8 +105,12 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> {
                 return false
             })
         },
-        getLabel(pickingId: PickingId) {
+        getLoci(pickingId: PickingId) {
+            // TODO
             return null
+        },
+        applyFlags(loci: Loci, action: FlagAction) {
+            // TODO
         }
     }
 }

+ 106 - 0
src/mol-geo/util/flag-data.ts

@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Unit } from 'mol-model/structure'
+import { ValueCell } from 'mol-util/value-cell'
+import { Vec2 } from 'mol-math/linear-algebra'
+import { TextureImage, createTextureImage } from 'mol-gl/renderable/util';
+
+export type FlagData = {
+    tFlag: ValueCell<TextureImage>
+    uFlagTexSize: ValueCell<Vec2>
+}
+
+export enum FlagAction {
+    Highlight,
+    RemoveHighlight,
+    Select,
+    Deselect,
+    ToggleSelect,
+    Clear
+}
+
+export function applyFlagAction(array: Uint8Array, start: number, end: number, action: FlagAction) {
+    let changed = false
+    for (let i = start; i < end; ++i) {
+        let v = array[i]
+        switch (action) {
+            case FlagAction.Highlight:
+                if (v % 2 === 0) {
+                    v += 1
+                    changed = true
+                }
+                break
+            case FlagAction.RemoveHighlight:
+                if (v % 2 !== 0) {
+                    v -= 1
+                    changed = true
+                } 
+                break
+            case FlagAction.Select:
+                v += 2
+                changed = true
+                break
+            case FlagAction.Deselect:
+                if (v >= 2) {
+                    v -= 2
+                    changed = true
+                }
+                break
+            case FlagAction.ToggleSelect:
+                if (v === 0) {
+                    v = 2
+                } else if (v === 1) {
+                    v = 3
+                } else if (v === 2) {
+                    v = 0
+                } else {
+                    v -= 2
+                }
+                changed = true
+                break
+            case FlagAction.Clear:
+                v = 0
+                changed = true
+                break
+        }
+        array[i] = v
+    }
+    return changed
+}
+
+export function createFlags(group: Unit.SymmetryGroup, flagData?: FlagData): FlagData {
+    const instanceCount = group.units.length
+    const elementCount = group.elements.length
+    const count = instanceCount * elementCount
+    const flags = flagData && flagData.tFlag.ref.value.array.length >= count
+        ? flagData.tFlag.ref.value
+        : createTextureImage(count, 1)
+    if (flagData) {
+        ValueCell.update(flagData.tFlag, flags)
+        ValueCell.update(flagData.uFlagTexSize, Vec2.create(flags.width, flags.height))
+        return flagData
+    } else {
+        return {
+            tFlag: ValueCell.create(flags),
+            uFlagTexSize: ValueCell.create(Vec2.create(flags.width, flags.height)),
+        }
+    }
+}
+
+const emptyFlagTexture = { array: new Uint8Array(1), width: 1, height: 1 }
+export function createEmptyFlags(flagData?: FlagData) {
+    if (flagData) {
+        ValueCell.update(flagData.tFlag, emptyFlagTexture)
+        ValueCell.update(flagData.uFlagTexSize, Vec2.create(1, 1))
+        return flagData
+    } else {
+        return {
+            tFlag: ValueCell.create(emptyFlagTexture),
+            uFlagTexSize: ValueCell.create(Vec2.create(1, 1)),
+        }
+    }
+}

+ 1 - 1
src/mol-gl/_spec/renderer.spec.ts

@@ -19,7 +19,7 @@ import { RenderableState } from '../renderable';
 import { createPointRenderObject } from '../render-object';
 import { PointValues } from '../renderable/point';
 import Scene from '../scene';
-import { createEmptyFlags } from 'mol-geo/representation/structure/utils';
+import { createEmptyFlags } from 'mol-geo/util/flag-data';
 
 // function writeImage(gl: WebGLRenderingContext, width: number, height: number) {
 //     const pixels = new Uint8Array(width * height * 4)

+ 13 - 1
src/mol-gl/shader/mesh.frag

@@ -76,8 +76,20 @@ void main() {
         gl_FragColor.rgb = finalColor;
         gl_FragColor.a = uAlpha;
 
-        if (vFlag == 1.0) {
+        // if (vFlag == 1.0) {
+        //     gl_FragColor.rgb = mix(vec3(1.0, 0.4, 0.6), gl_FragColor.rgb, 0.3);
+        // }
+
+        float flag = floor(vFlag * 255.0);
+
+        if (flag == 0.0) {
+            // diffuseColor = vec4( vColor, opacity );
+        } else if (mod(flag, 2.0) == 0.0) {
+            // diffuseColor = vec4(highlightColor, opacity);
             gl_FragColor.rgb = mix(vec3(1.0, 0.4, 0.6), gl_FragColor.rgb, 0.3);
+        } else {
+            // diffuseColor = vec4(selectionColor, opacity);
+            gl_FragColor.rgb = mix(vec3(0.2, 1.0, 0.1), gl_FragColor.rgb, 0.3);
         }
     #endif
 }

+ 8 - 1
src/mol-model/loci.ts

@@ -7,4 +7,11 @@
 import { Element } from './structure'
 import { Bond } from './structure/structure/unit/bonds'
 
-export type Loci =  Element.Loci | Bond.Loci
+/** A Loci that includes every loci */
+export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
+export type EveryLoci = typeof EveryLoci
+export function isEveryLoci(x: any): x is EveryLoci {
+    return !!x && x.kind === 'every-loci';
+}
+
+export type Loci =  Element.Loci | Bond.Loci | EveryLoci

+ 9 - 4
src/mol-model/structure/structure/element.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
@@ -36,13 +36,18 @@ namespace Element {
 
     export function property<T>(p: Property<T>) { return p; }
 
-    /** Represents multiple element locations */
+    /** Represents multiple element index locations */
     export interface Loci {
         readonly kind: 'element-loci',
-        readonly elements: ReadonlyArray<{ unit: Unit, elements: SortedArray }>
+        /** Access i-th element as unit.elements[indices[i]] */
+        readonly elements: ReadonlyArray<{
+            unit: Unit,
+            /** Indices into the unit.elements array */
+            indices: SortedArray
+        }>
     }
 
-    export function Loci(elements: ArrayLike<{ unit: Unit, elements: SortedArray }>): Loci {
+    export function Loci(elements: ArrayLike<{ unit: Unit, indices: SortedArray }>): Loci {
         return { kind: 'element-loci', elements: elements as Loci['elements'] };
     }
 

+ 10 - 2
src/mol-model/structure/structure/unit.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
@@ -33,9 +33,17 @@ namespace Unit {
         }
     }
 
-    // A group of units that differ only by symmetry operators.
+    /** A group of units that differ only by symmetry operators. */
     export type SymmetryGroup = { readonly elements: SortedArray, readonly units: ReadonlyArray<Unit> }
 
+    /** Find index of unit with given id, returns -1 if not found */
+    export function findUnitById(id: number, units: ReadonlyArray<Unit>) {
+        for (let i = 0, il = units.length; i < il; ++i) {
+            if (units[i].id === id) return i
+        }
+        return -1
+    }
+
     export interface Base {
         readonly id: number,
         // invariant ID stays the same even if the Operator/conformation changes.

+ 2 - 2
src/mol-view/label.ts

@@ -12,8 +12,8 @@ import { Loci } from 'mol-model/loci';
 export function labelFirst(loci: Loci) {
     if(Element.isLoci(loci)) {
         const e = loci.elements[0]
-        if (e && e.elements[0] !== undefined) {
-            return elementLabel(Element.Location(e.unit, e.elements[0]))
+        if (e && e.indices[0] !== undefined) {
+            return elementLabel(Element.Location(e.unit, e.indices[0]))
         }
     } else if (Bond.isLoci(loci)) {
         const bond = loci.bonds[0]

+ 1 - 1
src/mol-view/state/transform.ts

@@ -163,7 +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)
+        // StructureToBond.apply(ctx, structureEntity, props)
         return StructureToSpacefill.apply(ctx, structureEntity, props)
     })
 

+ 19 - 4
src/mol-view/viewer.ts

@@ -23,6 +23,8 @@ import Scene from 'mol-gl/scene';
 import { RenderVariant } from 'mol-gl/webgl/render-item';
 import { PickingId, decodeIdRGBA } from 'mol-geo/util/picking';
 import { labelFirst } from './label';
+import { FlagAction } from 'mol-geo/util/flag-data';
+import { EveryLoci } from 'mol-model/loci';
 
 interface Viewer {
     center: (p: Vec3) => void
@@ -81,14 +83,27 @@ namespace Viewer {
             const p = identify(x, y)
             let label = ''
             reprMap.forEach((roSet, repr) => {
+                repr.applyFlags(EveryLoci, FlagAction.RemoveHighlight)
                 const loci = repr.getLoci(p)
-                if (loci) label = labelFirst(loci)
-                repr.update({ hoverSelection: p }).run().then(() => {
+                if (loci) {
+                    label = labelFirst(loci)
+                    repr.applyFlags(loci, FlagAction.Highlight)
+                }
+                scene.update()
+                requestDraw()
+            })
+            identified.next(`Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`)
+        })
+        input.click.subscribe(({x, y}) => {
+            const p = identify(x, y)
+            reprMap.forEach((roSet, repr) => {
+                const loci = repr.getLoci(p)
+                if (loci) {
+                    repr.applyFlags(loci, FlagAction.ToggleSelect)
                     scene.update()
                     requestDraw()
-                })
+                }
             })
-            identified.next(`Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`)
         })
 
         const camera = PerspectiveCamera.create({