diff --git a/src/mol-geo/representation/index.ts b/src/mol-geo/representation/index.ts index 0d16ead2e558a541588c9f86757e5886cc7f2a8d..3bb9db0d29abeaa5fca0a07c80490c0fd0da4b6f 100644 --- a/src/mol-geo/representation/index.ts +++ b/src/mol-geo/representation/index.ts @@ -6,6 +6,7 @@ import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/render-object'; +import { PickingId, PickingInfo } from '../util/picking'; export interface RepresentationProps {} @@ -13,4 +14,5 @@ export interface Representation<D, P extends RepresentationProps = {}> { renderObjects: ReadonlyArray<RenderObject> create: (data: D, props?: P) => Task<void> update: (props: P) => Task<void> + getLabel: (pickingId: PickingId) => PickingInfo | null } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 11f21138d1a0604fc873c5a7cbfd6cef097d6746..508e4a45436aecb59dd0dfddb072bad9940a37b3 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -5,22 +5,26 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Structure, StructureSymmetry, Unit } from 'mol-model/structure'; +import { Structure, StructureSymmetry, Unit, Element, Queries } from 'mol-model/structure'; import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/render-object'; import { Representation, RepresentationProps } from '..'; import { ColorTheme } from '../../theme'; +import { PickingId, PickingInfo } from '../../util/picking'; export interface UnitsRepresentation<P> { renderObjects: ReadonlyArray<RenderObject> create: (group: Unit.SymmetryGroup, props: P) => Task<void> update: (props: P) => Task<boolean> + getLocation: (pickingId: PickingId) => Element.Location | null } 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> + getLocation: (pickingId: PickingId) => Element.Location | null + getLabel: (pickingId: PickingId) => PickingInfo | null } interface GroupRepresentation<T> { @@ -28,6 +32,36 @@ interface GroupRepresentation<T> { group: Unit.SymmetryGroup } +function label(loc: Element.Location) { + const model = loc.unit.model.label + const instance = loc.unit.label + let element = '' + + if (Unit.isAtomic(loc.unit)) { + const asym_id = Queries.props.chain.auth_asym_id(loc) + const seq_id = Queries.props.residue.auth_seq_id(loc) + const comp_id = Queries.props.residue.auth_comp_id(loc) + const atom_id = Queries.props.atom.auth_atom_id(loc) + element = `[${comp_id}]${seq_id}:${asym_id}.${atom_id}` + } else if (Unit.isCoarse(loc.unit)) { + const asym_id = Queries.props.coarse.asym_id(loc) + const seq_id_begin = Queries.props.coarse.seq_id_begin(loc) + const seq_id_end = Queries.props.coarse.seq_id_end(loc) + if (seq_id_begin === seq_id_end) { + const entityKey = Queries.props.coarse.entityKey(loc) + const seq = loc.unit.model.sequence.byEntityKey[entityKey] + const comp_id = seq.compId.value(seq_id_begin) + element = `[${comp_id}]${seq_id_begin}:${asym_id}` + } else { + element = `${seq_id_begin}-${seq_id_end}:${asym_id}` + } + } else { + element = 'unknown' + } + + return { label: `${model} ${instance} ${element}` } +} + export const DefaultStructureProps = { colorTheme: { name: 'instance-index' } as ColorTheme, alpha: 1, @@ -42,6 +76,14 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () = const groupReprs: GroupRepresentation<P>[] = [] // let currentProps: typeof DefaultStructureProps + function getLocation(pickingId: PickingId) { + for (let i = 0, il = groupReprs.length; i < il; ++i) { + const loc = groupReprs[i].repr.getLocation(pickingId) + if (loc) return loc + } + return null + } + return { renderObjects, create(structure: Structure, props: P = {} as P) { @@ -77,6 +119,11 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () = renderObjects.push(...repr.renderObjects) } }) + }, + getLocation, + getLabel(pickingId: PickingId) { + const loc = getLocation(pickingId) + return loc ? label(loc) : null } } } \ 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 19ae6a5fd9f82f8a258daaa09c3abb1a711ff6a5..a6498587ceb75378f16d7bcdcbab5d36dfe72515 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/src/mol-geo/representation/structure/point.ts @@ -18,6 +18,7 @@ import { createTransforms, createColors, createSizes } 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'; export const DefaultPointProps = { colorTheme: { name: 'instance-index' } as ColorTheme, @@ -50,7 +51,8 @@ export function createPointVertices(unit: Unit) { export default function Point(): UnitsRepresentation<PointProps> { const renderObjects: RenderObject[] = [] let points: PointRenderObject - let curProps = DefaultPointProps + let currentProps = DefaultPointProps + let currentGroup: Unit.SymmetryGroup let _units: ReadonlyArray<Unit> let _elements: SortedArray @@ -58,14 +60,16 @@ export default function Point(): UnitsRepresentation<PointProps> { return { renderObjects, create(group: Unit.SymmetryGroup, props: PointProps = {}) { + currentProps = Object.assign({}, DefaultPointProps, props) + return Task.create('Point.create', async ctx => { renderObjects.length = 0 // clear - curProps = { ...DefaultPointProps, ...props } + currentGroup = group _units = group.units _elements = group.elements; - const { colorTheme, sizeTheme } = curProps + const { colorTheme, sizeTheme } = currentProps const elementCount = _elements.length const vertexMap = VertexMap.create( @@ -119,8 +123,8 @@ export default function Point(): UnitsRepresentation<PointProps> { return Task.create('Point.update', async ctx => { if (!points || !_units || !_elements) return false - const newProps = { ...curProps, ...props } - if (deepEqual(curProps, newProps)) { + const newProps = { ...currentProps, ...props } + if (deepEqual(currentProps, newProps)) { console.log('props identical, nothing to change') return true } @@ -135,20 +139,30 @@ export default function Point(): UnitsRepresentation<PointProps> { // fillSerial(new Uint32Array(elementCount + 1)) // ) - if (!deepEqual(curProps.colorTheme, newProps.colorTheme)) { - console.log('colorTheme changed', curProps.colorTheme, newProps.colorTheme) + if (!deepEqual(currentProps.colorTheme, newProps.colorTheme)) { + console.log('colorTheme changed', currentProps.colorTheme, newProps.colorTheme) // await ctx.update('Computing point colors'); // const color = createColors(_units, _elementGroup, vertexMap, newProps.colorTheme) // ValueCell.update(points.props.color, color) } - if (!deepEqual(curProps.sizeTheme, newProps.sizeTheme)) { - console.log('sizeTheme changed', curProps.sizeTheme, newProps.sizeTheme) + if (!deepEqual(currentProps.sizeTheme, newProps.sizeTheme)) { + console.log('sizeTheme changed', currentProps.sizeTheme, newProps.sizeTheme) } - curProps = newProps + currentProps = newProps return false }) + }, + getLocation(pickingId: PickingId) { + const { objectId, instanceId, elementId } = pickingId + if (points.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 b70a9bef3356a0c38d8284bb6413825df7edbe7b..fe2e6141cdad467942a0201c89c5bbf64245028a 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -22,6 +22,7 @@ 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'; export const DefaultSpacefillProps = { ...DefaultStructureProps, @@ -70,9 +71,7 @@ function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) { } } - const _mesh = meshBuilder.getMesh() - console.log(_mesh) - return _mesh + return meshBuilder.getMesh() }) } @@ -171,6 +170,16 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { currentProps = newProps return true }) + }, + getLocation(pickingId: PickingId) { + const { objectId, instanceId, elementId } = pickingId + if (spheres.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/volume/index.ts b/src/mol-geo/representation/volume/index.ts index 5baece8ad324f559ef85c58e6acb23128e2816fd..f8edfdaad75d5dbce7d9e2c4fad7a793b9d02d56 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/src/mol-geo/representation/volume/index.ts @@ -8,17 +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'; 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> + getLabel: (pickingId: PickingId) => PickingInfo | null } export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentation<P>): VolumeRepresentation<P> { @@ -35,6 +38,9 @@ export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentat }, update(props: P) { return Task.create('VolumeRepresentation.update', async ctx => {}) + }, + getLabel(pickingId: PickingId) { + return null } } } \ 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 7e528a99a235dfdb1bf1f9f94535cf9fd4c746a8..16071c84a0f802cd7d7a25bae157c5ce95b78631 100644 --- a/src/mol-geo/representation/volume/surface.ts +++ b/src/mol-geo/representation/volume/surface.ts @@ -17,6 +17,7 @@ import { Mat4 } from 'mol-math/linear-algebra'; import { createUniformColor } from '../../util/color-data'; import { getMeshData } from '../../util/mesh-data'; import { RenderableState, MeshValues } from 'mol-gl/renderable'; +import { PickingId } from '../../util/picking'; export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) { return Task.create<Mesh>('Volume Surface', async ctx => { @@ -99,6 +100,9 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> { // TODO return false }) + }, + getLabel(pickingId: PickingId) { + return null } } } diff --git a/src/mol-geo/util/color-data.ts b/src/mol-geo/util/color-data.ts index 09dfc525f08bc14a226198cc67f285cf7dcb01f1..0da692da97f6c69e077ea7409b4890be547bfc74 100644 --- a/src/mol-geo/util/color-data.ts +++ b/src/mol-geo/util/color-data.ts @@ -62,7 +62,6 @@ export function createAttributeColor(props: AttributeColorProps, colorData?: Col } } if (colorData) { - console.log('update colordata attribute') ValueCell.update(colorData.aColor, colors) if (colorData.dColorType.ref.value !== 'attribute') { ValueCell.update(colorData.dColorType, 'attribute') @@ -81,7 +80,6 @@ export function createAttributeColor(props: AttributeColorProps, colorData?: Col export function createTextureColor(colors: TextureImage, type: ColorType, colorData?: ColorData): ColorData { if (colorData) { - console.log('update colordata texture') ValueCell.update(colorData.tColor, colors) ValueCell.update(colorData.uColorTexSize, Vec2.create(colors.width, colors.height)) if (colorData.dColorType.ref.value !== type) { diff --git a/src/mol-geo/util/picking.ts b/src/mol-geo/util/picking.ts new file mode 100644 index 0000000000000000000000000000000000000000..00d12d6fd6d297ad232fb81720eb0dc8dd63f252 --- /dev/null +++ b/src/mol-geo/util/picking.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +function decodeFloatRGBA(r: number, g: number, b: number) { + r = Math.floor(r) + g = Math.floor(g) + b = Math.floor(b) + return r * 256 * 256 + g * 256 + b +} + +export function decodeIdRGBA(r: number, g: number, b: number) { + return decodeFloatRGBA(r, g, b) - 1 +} + +export interface PickingId { + objectId: number + instanceId: number + elementId: number +} + +export interface PickingInfo { + label: string + data?: any +} \ No newline at end of file diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 9c15ddc61bffa34364f9c913f6f8d6429cb78449..0f090610cf8ebeb354a313f121aa99f7e091b576 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -21,23 +21,7 @@ import { Representation } from 'mol-geo/representation'; import { createRenderTarget } from 'mol-gl/webgl/render-target'; import Scene from 'mol-gl/scene'; import { RenderVariant } from 'mol-gl/webgl/render-item'; - -function decodeFloatRGBA(r: number, g: number, b: number) { - r = Math.floor(r) - g = Math.floor(g) - b = Math.floor(b) - return r * 256 * 256 + g * 256 + b -} - -function decodeIdRGBA(r: number, g: number, b: number) { - return decodeFloatRGBA(r, g, b) - 1 -} - -interface PickingId { - objectId: number - instanceId: number - elementId: number -} +import { PickingId, decodeIdRGBA } from 'mol-geo/util/picking'; interface Viewer { center: (p: Vec3) => void @@ -94,7 +78,12 @@ namespace Viewer { input.resize.subscribe(handleResize) input.move.subscribe(({x, y}) => { const p = identify(x, y) - identified.next(`Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}`) + let label = '' + reprMap.forEach((roSet, repr) => { + const info = repr.getLabel(p) + if (info) label = info.label + }) + identified.next(`Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`) }) const camera = PerspectiveCamera.create({