diff --git a/src/mol-geo/representation/index.ts b/src/mol-geo/representation/index.ts index ed37c20b33151a6e943fd15269a96798c8fff7bf..7c2d2de3657ed3bdbbf102bf164848419dbd1a38 100644 --- a/src/mol-geo/representation/index.ts +++ b/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 } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/bond.ts b/src/mol-geo/representation/structure/bond.ts index ee73d7bd93cf3b5bd49d7b0ec6e5e57cd907d58d..1495ab1c0c89e5a25ae3b4db11f896f657c39d93 100644 --- a/src/mol-geo/representation/structure/bond.ts +++ b/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 } } } diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 6512d2f677841370afeaa71e4e2545f9f135b3d8..32dfa0a4bbadb1bf11e5f9a29a176adf9b614a73 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/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) + } + } } } \ 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 31aa640e5fc559c12cc85d80f75e97bfd583f83b..a45facb642baaa71345cfa2c99ad20f6ba072200 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/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) } } } diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 3411e43bfc4e9167304273d03199b09e33d38056..d1056a033e7986d7654e47d0987f9001685e8815 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/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) } } } diff --git a/src/mol-geo/representation/structure/utils.ts b/src/mol-geo/representation/structure/utils.ts index 5771c3f122504e4413f1423ce0e73a8d53a92f6a..a0a17964c46a0bbba42c4a38281a441c036455ac 100644 --- a/src/mol-geo/representation/structure/utils.ts +++ b/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) + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts index b38ba274915eefd19b80d73b4cb98363f8a1b795..cc702bfbd9f84a35830a1332ff173cfa8e375a2f 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/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 } } } \ No newline at end of file diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts index fab4c945407a158c3383537f9c1c8af0acf283d6..5bed7c8b5b05d250f6c3c99c1b8538a02e426305 100644 --- a/src/mol-geo/representation/volume/surface.ts +++ b/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 } } } diff --git a/src/mol-geo/util/flag-data.ts b/src/mol-geo/util/flag-data.ts new file mode 100644 index 0000000000000000000000000000000000000000..206f1aeae7ea311e45ce112440ae40d4d78a063d --- /dev/null +++ b/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)), + } + } +} \ No newline at end of file diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index 2b57d415255f4c8220d6520c4a4810430a89c8fb..0d087d672c95cb91dffe70c523355b8e98db2c28 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/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) diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index 401a0e46bc5aa9f1d6db723aeebe117e5846b38a..6350ea200a8ba7b950f44554b53a54a2bb03f702 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/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 } \ No newline at end of file diff --git a/src/mol-model/loci.ts b/src/mol-model/loci.ts index 81fba647830710cb78043dbc1ccb6786a441a6aa..3d10cb93063593a236d649b6a2911b492c00d300 100644 --- a/src/mol-model/loci.ts +++ b/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 \ No newline at end of file +/** 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 \ No newline at end of file diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts index 270fdcdf764fa6ee1174c3b539f57243c300a997..c9645caa8af484622d96187ebfab117456cd76c9 100644 --- a/src/mol-model/structure/structure/element.ts +++ b/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'] }; } diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index 5c34fd555248e6b3c2cf752446930b7d47116f6e..753d73eb211feef7b1b8921352a9daf9e2d5f7c4 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/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. diff --git a/src/mol-view/label.ts b/src/mol-view/label.ts index bc6e7d76835653d13cc7c4822c4d82e14b5cb19f..311abab26217c917c0960f991a1815b7dcdb9b28 100644 --- a/src/mol-view/label.ts +++ b/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] diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts index 6ed6de1d466b1ec2cb66ebcf0c9b0384e10ea23d..ed4adbc48df82f0ac8af9be9720dd2bd5eeda63f 100644 --- a/src/mol-view/state/transform.ts +++ b/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) }) diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index c0b89c6fed96494b4e9c8feed020bab948dcb4f4..06671bcfe105e537b5a6f21692b227879ca1c13b 100644 --- a/src/mol-view/viewer.ts +++ b/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({