diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index c884fc4fdf7f653a496510db7b205f8c8604ec37..2a9f5896f12bf3d25aa77133e24288af3718eeb0 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -244,7 +244,7 @@ namespace Canvas3D { } async function identify(x: number, y: number): Promise<PickingId | undefined> { - if (pickDirty) return undefined + if (pickDirty || isPicking) return undefined isPicking = true @@ -257,25 +257,27 @@ namespace Canvas3D { const yp = Math.round(y * pickScale) objectPickTarget.bind() - await webgl.readPixelsAsync(xp, yp, 1, 1, buffer) + // TODO slow in Chrome, ok in FF; doesn't play well with gpu surface calc + // await webgl.readPixelsAsync(xp, yp, 1, 1, buffer) + webgl.readPixels(xp, yp, 1, 1, buffer) const objectId = decodeIdRGB(buffer[0], buffer[1], buffer[2]) + if (objectId === -1) return instancePickTarget.bind() - await webgl.readPixels(xp, yp, 1, 1, buffer) + // await webgl.readPixelsAsync(xp, yp, 1, 1, buffer) + webgl.readPixels(xp, yp, 1, 1, buffer) const instanceId = decodeIdRGB(buffer[0], buffer[1], buffer[2]) + if (instanceId === -1) return groupPickTarget.bind() - await webgl.readPixels(xp, yp, 1, 1, buffer) + // await webgl.readPixelsAsync(xp, yp, 1, 1, buffer) + webgl.readPixels(xp, yp, 1, 1, buffer) const groupId = decodeIdRGB(buffer[0], buffer[1], buffer[2]) + if (groupId === -1) return isPicking = false - // TODO - if (objectId === -1 || instanceId === -1 || groupId === -1) { - return { objectId: -1, instanceId: -1, groupId: -1 } - } else { - return { objectId, instanceId, groupId } - } + return { objectId, instanceId, groupId } } function add(repr: Representation.Any) { diff --git a/src/mol-geo/geometry/geometry.ts b/src/mol-geo/geometry/geometry.ts index 8533bc34244aa8a4691e389d249d7e50655aeee2..95e8611c3eb4f5525912744873bdebfcb133bdfc 100644 --- a/src/mol-geo/geometry/geometry.ts +++ b/src/mol-geo/geometry/geometry.ts @@ -62,7 +62,6 @@ export namespace Geometry { export const Params = { alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity' }), - depthMask: PD.Boolean(true), useFog: PD.Boolean(false), highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)), selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)), @@ -105,12 +104,11 @@ export function createRenderableState(props: PD.Values<Geometry.Params>): Render return { visible: true, pickable: true, - depthMask: props.depthMask } } export function updateRenderableState(state: RenderableState, props: PD.Values<Geometry.Params>) { - state.depthMask = props.depthMask + } // diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index e62c5415f7be59b638ff97167f2ca012d6d2c34a..db65cd76d9c5b9bc2d7710ce15f4b9bf24df5b6e 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -86,7 +86,6 @@ function createPoints() { const state: RenderableState = { visible: true, pickable: true, - depthMask: true, } return createPointsRenderObject(values, state) diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index 6d9d55ac3e5612ea01df7663062484f09dcf4503..133e62633677061fe68189e0f35242c656cb62ee 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -14,7 +14,6 @@ import { ValueCell } from 'mol-util'; export type RenderableState = { visible: boolean pickable: boolean - depthMask: boolean } export interface Renderable<T extends RenderableValues> { diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 27ce4e40668fb0b78c587187fbb6564d0fb006f3..46837f43c40bd91cc4a91c54bdae8918815011cd 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -35,6 +35,8 @@ export const DirectVolumeSchema = { elements: ElementsSpec('uint32'), uAlpha: UniformSpec('f'), + uHighlightColor: UniformSpec('v3'), + uSelectColor: UniformSpec('v3'), dUseFog: DefineSpec('boolean'), uIsoValue: UniformSpec('f'), diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 26c1845e523e11baea123a2c13a9569b7222a0a9..aee976db3e33be8abc9e4b4a9a5c9866590a8ab3 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -139,7 +139,7 @@ namespace Renderer { gl.cullFace(gl.BACK) } - gl.depthMask(r.state.depthMask) + gl.depthMask(r.opaque) r.render(variant) } diff --git a/src/mol-gl/shader/direct-volume.frag b/src/mol-gl/shader/direct-volume.frag index 7baa242c02aae1f90c6b47ebf9678152ff1f3cef..d94f580286440e3c29ef0aaf57e391d8a289b5a7 100644 --- a/src/mol-gl/shader/direct-volume.frag +++ b/src/mol-gl/shader/direct-volume.frag @@ -11,7 +11,6 @@ varying vec3 unitCoord; varying vec3 origPos; varying float instance; -uniform float uAlpha; uniform mat4 uInvView; uniform float uIsoValue; uniform vec3 uGridDim; @@ -26,6 +25,10 @@ uniform vec3 uSelectColor; uniform vec2 uMarkerTexDim; uniform sampler2D tMarker; +uniform float uAlpha; +uniform float uPickingAlphaThreshold; +uniform int uPickable; + #if defined(dGridTexType_2d) precision mediump sampler2D; uniform sampler2D tGridTex; @@ -126,7 +129,9 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) { #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking) if (uAlpha < uPickingAlphaThreshold) discard; // ignore so the element below can be picked - #else + if (uPickable == 0) + return vec4(0.0, 0.0, 0.0, 1.0); // set to empty picking id + #endif #if defined(dColorType_objectPicking) return vec4(encodeIdRGB(float(uObjectId)), 1.0); diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index fd74e707c6e300b2de593f1dd52fe63d0591c242..588c63936ddc7cd59b27a6853f4b684e09847b82 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -55,39 +55,42 @@ function unbindFramebuffer(gl: GLRenderingContext) { const tmpPixel = new Uint8Array(1 * 4); -function fence(gl: WebGL2RenderingContext) { +function checkSync(gl: WebGL2RenderingContext, sync: WebGLSync, resolve: () => void) { + if (gl.getSyncParameter(sync, gl.SYNC_STATUS) === gl.SIGNALED) { + gl.deleteSync(sync) + resolve() + } else { + Scheduler.setImmediate(checkSync, gl, sync, resolve) + } +} + +function fence(gl: WebGL2RenderingContext, resolve: () => void) { + const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0) + if (!sync) { + console.warn('Could not create a WebGLSync object') + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel) + resolve() + } else { + Scheduler.setImmediate(checkSync, gl, sync, resolve) + } +} + +let SentWebglSyncObjectNotSupportedInWebglMessage = false +function waitForGpuCommandsComplete(gl: GLRenderingContext): Promise<void> { return new Promise(resolve => { - gl.finish() - const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0) - if (!sync) { - console.warn('could not create a WebGL2 sync object') - gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel) - resolve() + if (isWebGL2(gl)) { + fence(gl, resolve) } else { - gl.flush(); // Ensure the fence is submitted. - const check = () => { - const status = gl.getSyncParameter(sync, gl.SYNC_STATUS) - if (status === gl.SIGNALED) { - gl.deleteSync(sync) - resolve() - } else { - Scheduler.setImmediate(check, 0) - } + if (!SentWebglSyncObjectNotSupportedInWebglMessage) { + console.info('Sync object not supported in WebGL') + SentWebglSyncObjectNotSupportedInWebglMessage = true } - Scheduler.setImmediate(check, 0) + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel) + resolve() } }) } -async function waitForGpuCommandsComplete(gl: GLRenderingContext) { - if (isWebGL2(gl)) { - await fence(gl) - } else { - console.info('webgl sync object not supported in webgl 1') - gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel) - } -} - export function createImageData(buffer: ArrayLike<number>, width: number, height: number) { const w = width * 4 const h = height @@ -198,17 +201,34 @@ export function createContext(gl: GLRenderingContext): WebGLContext { let readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void> if (isWebGL2(gl)) { const pbo = gl.createBuffer() - readPixelsAsync = async (x: number, y: number, width: number, height: number, buffer: Uint8Array) => { + let _buffer: Uint8Array | undefined = void 0 + let _resolve: (() => void) | undefined = void 0 + let _reading = false + + const bindPBO = () => { + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo) + gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, _buffer!) + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null) + _reading = false + _resolve!() + _resolve = void 0 + _buffer = void 0 + } + readPixelsAsync = (x: number, y: number, width: number, height: number, buffer: Uint8Array): Promise<void> => new Promise<void>((resolve, reject) => { + if (_reading) { + reject('Can not call multiple readPixelsAsync at the same time') + return + } + _reading = true; gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo) gl.bufferData(gl.PIXEL_PACK_BUFFER, width * height * 4, gl.STREAM_READ) gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, 0) gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null) // need to unbind/bind PBO before/after async awaiting the fence - await fence(gl) - gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo) - gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, buffer) - gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null) - } + _resolve = resolve + _buffer = buffer + fence(gl, bindPBO) + }) } else { readPixelsAsync = async (x: number, y: number, width: number, height: number, buffer: Uint8Array) => { gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer) diff --git a/src/mol-gl/webgl/program.ts b/src/mol-gl/webgl/program.ts index b7eb34b2664aa1c3a795e2517badffcc959ddbca..6825b47bba9b1c4dafd4e8d5aa5da7585cbeec24 100644 --- a/src/mol-gl/webgl/program.ts +++ b/src/mol-gl/webgl/program.ts @@ -68,6 +68,9 @@ export function createProgram(ctx: WebGLContext, props: ProgramProps): Program { vertShaderRef.value.attach(program) fragShaderRef.value.attach(program) gl.linkProgram(program) + if (!gl.getProgramParameter(program, gl.LINK_STATUS)){ + throw new Error(`Could not compile WebGL program. \n\n${gl.getProgramInfoLog(program)}`); + } const uniformUpdaters = getUniformUpdaters(ctx, program, schema) const attributeLocations = getAttributeLocations(ctx, program, schema) diff --git a/src/mol-math/geometry/gaussian-density/cpu.ts b/src/mol-math/geometry/gaussian-density/cpu.ts index 1e1d0cdb35a16c476c17f3071c1f669d847573ad..ad8020c5b545b00ff3a376494a2dda572320fc72 100644 --- a/src/mol-math/geometry/gaussian-density/cpu.ts +++ b/src/mol-math/geometry/gaussian-density/cpu.ts @@ -38,7 +38,7 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position const delta = getDelta(Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad)), resolution) const dim = Vec3.zero() Vec3.ceil(dim, Vec3.mul(dim, extent, delta)) - console.log('grid dim', dim) + // console.log('grid dim', dim) const space = Tensor.Space(dim, [0, 1, 2], Float32Array) const data = space.create() @@ -63,7 +63,7 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position const gridPad = 1 / Math.max(...delta) - console.time('gaussian density cpu') + // console.time('gaussian density cpu') for (let i = 0; i < n; ++i) { const j = OrderedSet.getAt(indices, i) @@ -105,7 +105,7 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position await ctx.update({ message: 'filling density grid', current: i, max: n }) } } - console.timeEnd('gaussian density cpu') + // console.timeEnd('gaussian density cpu') const transform = Mat4.identity() Mat4.fromScaling(transform, Vec3.inverse(Vec3.zero(), delta)) diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts index d0429a36dfee714b53ba6de3a59c0cae93856e9a..07afeb907ef77c908083ff5a7a4974202bbdac0c 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/src/mol-math/geometry/gaussian-density/gpu.ts @@ -233,7 +233,6 @@ function getGaussianDensityRenderObject(webgl: WebGLContext, drawCount: number, const state: RenderableState = { visible: true, pickable: false, - depthMask: false } const renderObject = createGaussianDensityRenderObject(values, state) diff --git a/src/mol-model/structure/structure/unit/links/data.ts b/src/mol-model/structure/structure/unit/links/data.ts index 1ee6243c4b6b2ecda3473a0240a36d63e43a5ef1..05716bc1b8c7024cedf73ca0b76a30e3d5565ec5 100644 --- a/src/mol-model/structure/structure/unit/links/data.ts +++ b/src/mol-model/structure/structure/unit/links/data.ts @@ -23,6 +23,7 @@ class InterUnitBonds { /** Array of inter-unit bonds */ readonly bonds: ReadonlyArray<InterUnitBonds.Bond> private readonly bondKeyIndex: Map<string, number> + private readonly elementKeyIndex: Map<string, number[]> /** Get an array of unit-pair-bonds that are linked to the given unit */ getLinkedUnits(unit: Unit): ReadonlyArray<InterUnitBonds.UnitPairBonds> { @@ -32,8 +33,8 @@ class InterUnitBonds { /** Index into this.bonds */ getBondIndex(indexA: StructureElement.UnitIndex, unitA: Unit, indexB: StructureElement.UnitIndex, unitB: Unit): number { - const key = InterUnitBonds.getBondKey(indexA, unitA, indexB, unitB) - const index = this.bondKeyIndex.get(key) + const bondKey = InterUnitBonds.getBondKey(indexA, unitA, indexB, unitB) + const index = this.bondKeyIndex.get(bondKey) return index !== undefined ? index : -1 } @@ -48,26 +49,44 @@ class InterUnitBonds { return this.getBond(l.aIndex, l.aUnit, l.bIndex, l.bUnit); } + /** Indices into this.bonds */ + getBondIndices(index: StructureElement.UnitIndex, unit: Unit): ReadonlyArray<number> { + const elementKey = InterUnitBonds.getElementKey(index, unit) + const indices = this.elementKeyIndex.get(elementKey) + return indices !== undefined ? indices : [] + } + constructor(private map: Map<number, InterUnitBonds.UnitPairBonds[]>) { let count = 0 const bonds: (InterUnitBonds.Bond)[] = [] const bondKeyIndex = new Map<string, number>() + const elementKeyIndex = new Map<string, number[]>() + this.map.forEach(pairBondsArray => { pairBondsArray.forEach(pairBonds => { count += pairBonds.bondCount pairBonds.linkedElementIndices.forEach(indexA => { pairBonds.getBonds(indexA).forEach(bondInfo => { const { unitA, unitB } = pairBonds - const key = InterUnitBonds.getBondKey(indexA, unitA, bondInfo.indexB, unitB) - bondKeyIndex.set(key, bonds.length) + + const bondKey = InterUnitBonds.getBondKey(indexA, unitA, bondInfo.indexB, unitB) + bondKeyIndex.set(bondKey, bonds.length) + + const elementKey = InterUnitBonds.getElementKey(indexA, unitA) + const e = elementKeyIndex.get(elementKey) + if (e === undefined) elementKeyIndex.set(elementKey, [bonds.length]) + else e.push(bonds.length) + bonds.push({ ...bondInfo, indexA, unitA, unitB }) }) }) }) }) + this.bondCount = count this.bonds = bonds this.bondKeyIndex = bondKeyIndex + this.elementKeyIndex = elementKeyIndex } } @@ -102,15 +121,19 @@ namespace InterUnitBonds { export interface Bond { readonly unitA: Unit.Atomic, readonly unitB: Unit.Atomic, - readonly indexA: number, - readonly indexB: number, + readonly indexA: StructureElement.UnitIndex, + readonly indexB: StructureElement.UnitIndex, readonly order: number, readonly flag: LinkType.Flag } - export function getBondKey(indexA: number, unitA: Unit, indexB: number, unitB: Unit) { + export function getBondKey(indexA: StructureElement.UnitIndex, unitA: Unit, indexB: StructureElement.UnitIndex, unitB: Unit) { return `${indexA}|${unitA.id}|${indexB}|${unitB.id}` } + + export function getElementKey(index: StructureElement.UnitIndex, unit: Unit) { + return `${index}|${unit.id}` + } } const emptyArray: any[] = []; diff --git a/src/mol-repr/structure/representation/ball-and-stick.ts b/src/mol-repr/structure/representation/ball-and-stick.ts index d28d478975ce8b18f360dfbbf783ce2b89815bb3..4ea997482b639aaa597c14fc2674ebf4200816ef 100644 --- a/src/mol-repr/structure/representation/ball-and-stick.ts +++ b/src/mol-repr/structure/representation/ball-and-stick.ts @@ -30,7 +30,8 @@ export const BallAndStickParams = { ...IntraUnitLinkParams, ...InterUnitLinkParams, unitKinds: PD.MultiSelect<UnitKind>(['atomic'], UnitKindOptions), - sizeFactor: PD.Numeric(0.2, { min: 0.01, max: 10, step: 0.01 }), + sizeFactor: PD.Numeric(0.3, { min: 0.01, max: 10, step: 0.01 }), + sizeAspectRatio: PD.Numeric(2/3, { min: 0.01, max: 3, step: 0.01 }), colorTheme: PD.Mapped('polymer-index', BuiltInColorThemeOptions, name => PD.Group((BuiltInColorThemes as { [k: string]: ColorTheme.Provider<any> })[name].getParams({}))), visuals: PD.MultiSelect<BallAndStickVisualName>(['element-sphere', 'intra-link', 'inter-link'], BallAndStickVisualOptions), } diff --git a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts index 05d5a04b0d66afd2c4bf649fb66826835538fe13..a85dddd51386a2f05e1554529d4363533f48789d 100644 --- a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts @@ -51,7 +51,6 @@ async function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: export const CarbohydrateLinkParams = { ...UnitsMeshParams, ...LinkCylinderParams, - detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }), linkSizeFactor: PD.Numeric(0.3, { min: 0, max: 3, step: 0.01 }), } export type CarbohydrateLinkParams = typeof CarbohydrateLinkParams @@ -64,7 +63,10 @@ export function CarbohydrateLinkVisual(): ComplexVisual<CarbohydrateLinkParams> getLoci: getLinkLoci, mark: markLink, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<CarbohydrateLinkParams>, currentProps: PD.Values<CarbohydrateLinkParams>) => { - state.createGeometry = newProps.radialSegments !== currentProps.radialSegments + state.createGeometry = ( + newProps.linkSizeFactor !== currentProps.linkSizeFactor || + newProps.radialSegments !== currentProps.radialSegments + ) } }) } diff --git a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts index 7a5acbf598fc3d6b1241632712d95c97ee30358e..9a42ca5f74712690b84fe5aa77a2ec001c660391 100644 --- a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts @@ -159,7 +159,10 @@ export function CarbohydrateSymbolVisual(): ComplexVisual<CarbohydrateSymbolPara getLoci: getCarbohydrateLoci, mark: markCarbohydrate, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<CarbohydrateSymbolParams>, currentProps: PD.Values<CarbohydrateSymbolParams>) => { - state.createGeometry = newProps.detail !== currentProps.detail + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor || + newProps.detail !== currentProps.detail + ) } }) } diff --git a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts index 7d594fd788a192d1fa5fb1df4e2ec6f2bff9ca65..3c4d57853b4140d8fba7d23ae49c39c39d42dd7c 100644 --- a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts @@ -61,7 +61,6 @@ async function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, st export const CarbohydrateTerminalLinkParams = { ...UnitsMeshParams, ...LinkCylinderParams, - detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }), linkSizeFactor: PD.Numeric(0.3, { min: 0, max: 3, step: 0.01 }), } export type CarbohydrateTerminalLinkParams = typeof CarbohydrateTerminalLinkParams @@ -74,7 +73,10 @@ export function CarbohydrateTerminalLinkVisual(): ComplexVisual<CarbohydrateTerm getLoci: getTerminalLinkLoci, mark: markTerminalLink, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<CarbohydrateTerminalLinkParams>, currentProps: PD.Values<CarbohydrateTerminalLinkParams>) => { - state.createGeometry = newProps.radialSegments !== currentProps.radialSegments + state.createGeometry = ( + newProps.linkSizeFactor !== currentProps.linkSizeFactor || + newProps.radialSegments !== currentProps.radialSegments + ) } }) } diff --git a/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts b/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts index 1b1d06ea80011194fc9b8b3993a4cc0e8133f1c2..bd1733ea4df225bf7dfa264c64e4c7d518022804 100644 --- a/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts +++ b/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts @@ -7,7 +7,7 @@ import { Link, Structure, StructureElement } from 'mol-model/structure'; import { ComplexVisual } from '../representation'; import { VisualUpdateState } from '../../util'; -import { LinkCylinderProps, createLinkCylinderMesh, LinkCylinderParams } from './util/link'; +import { createLinkCylinderMesh, LinkCylinderParams } from './util/link'; import { Vec3 } from 'mol-math/linear-algebra'; import { Loci, EmptyLoci } from 'mol-model/loci'; import { ComplexMeshVisual, ComplexMeshParams } from '../complex-visual'; @@ -21,10 +21,11 @@ import { PickingId } from 'mol-geo/geometry/picking'; import { VisualContext } from 'mol-repr/representation'; import { Theme } from 'mol-theme/theme'; -async function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) { +async function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CrossLinkRestraintParams>, mesh?: Mesh) { const crossLinks = structure.crossLinkRestraints if (!crossLinks.count) return Mesh.createEmpty(mesh) + const { sizeFactor } = props const location = StructureElement.create() @@ -43,7 +44,7 @@ async function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structur const b = crossLinks.pairs[edgeIndex] location.unit = b.unitA location.element = b.unitA.elements[b.indexA] - return theme.size.size(location) + return theme.size.size(location) * sizeFactor } } @@ -53,6 +54,7 @@ async function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structur export const CrossLinkRestraintParams = { ...ComplexMeshParams, ...LinkCylinderParams, + sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }), } export type CrossLinkRestraintParams = typeof CrossLinkRestraintParams @@ -64,7 +66,10 @@ export function CrossLinkRestraintVisual(): ComplexVisual<CrossLinkRestraintPara getLoci: getLinkLoci, mark: markLink, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<CrossLinkRestraintParams>, currentProps: PD.Values<CrossLinkRestraintParams>) => { - state.createGeometry = newProps.radialSegments !== currentProps.radialSegments + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor || + newProps.radialSegments !== currentProps.radialSegments + ) } }) } diff --git a/src/mol-repr/structure/visual/element-sphere.ts b/src/mol-repr/structure/visual/element-sphere.ts index 8f80022307ce1439079883cc4d93486d565ea14a..50c2abc39217083513b82f5cd984b3b2ac853e11 100644 --- a/src/mol-repr/structure/visual/element-sphere.ts +++ b/src/mol-repr/structure/visual/element-sphere.ts @@ -28,7 +28,10 @@ export function ElementSphereVisual(): UnitsVisual<ElementSphereParams> { getLoci: getElementLoci, mark: markElement, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<ElementSphereParams>, currentProps: PD.Values<ElementSphereParams>) => { - state.createGeometry = newProps.detail !== currentProps.detail + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor || + newProps.detail !== currentProps.detail + ) } }) } \ No newline at end of file diff --git a/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts b/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts index f85233d3541260ded2037f1c37c814e17e00be24..bf21c9f87138f8c026367b069bd621dc746edb0e 100644 --- a/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts +++ b/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts @@ -11,7 +11,7 @@ import { createLinkCylinderMesh, LinkIterator, LinkCylinderParams } from './util import { Vec3 } from 'mol-math/linear-algebra'; import { Loci, EmptyLoci } from 'mol-model/loci'; import { ComplexMeshVisual, ComplexMeshParams } from '../complex-visual'; -import { Interval } from 'mol-data/int'; +import { Interval, OrderedSet } from 'mol-data/int'; import { BitFlags } from 'mol-util'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import { Mesh } from 'mol-geo/geometry/mesh/mesh'; @@ -22,7 +22,7 @@ import { Theme } from 'mol-theme/theme'; async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitLinkParams>, mesh?: Mesh) { const links = structure.links const { bondCount, bonds } = links - const { sizeFactor } = props + const { sizeFactor, sizeAspectRatio } = props if (!bondCount) return Mesh.createEmpty(mesh) @@ -43,7 +43,7 @@ async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: St const b = bonds[edgeIndex] location.unit = b.unitA location.element = b.unitA.elements[b.indexA] - return theme.size.size(location) * sizeFactor + return theme.size.size(location) * sizeFactor * sizeAspectRatio } } @@ -53,7 +53,8 @@ async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: St export const InterUnitLinkParams = { ...ComplexMeshParams, ...LinkCylinderParams, - sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }), + sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }), + sizeAspectRatio: PD.Numeric(2/3, { min: 0, max: 3, step: 0.01 }), } export type InterUnitLinkParams = typeof InterUnitLinkParams @@ -65,9 +66,13 @@ export function InterUnitLinkVisual(): ComplexVisual<InterUnitLinkParams> { getLoci: getLinkLoci, mark: markLink, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitLinkParams>, currentProps: PD.Values<InterUnitLinkParams>) => { - if (newProps.linkScale !== currentProps.linkScale) state.createGeometry = true - if (newProps.linkSpacing !== currentProps.linkSpacing) state.createGeometry = true - if (newProps.radialSegments !== currentProps.radialSegments) state.createGeometry = true + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor || + newProps.sizeAspectRatio !== currentProps.sizeAspectRatio || + newProps.radialSegments !== currentProps.radialSegments || + newProps.linkScale !== currentProps.linkScale || + newProps.linkSpacing !== currentProps.linkSpacing + ) } }) } @@ -80,6 +85,10 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) { Link.Location( bond.unitA, bond.indexA as StructureElement.UnitIndex, bond.unitB, bond.indexB as StructureElement.UnitIndex + ), + Link.Location( + bond.unitB, bond.indexB as StructureElement.UnitIndex, + bond.unitA, bond.indexA as StructureElement.UnitIndex ) ]) } @@ -88,11 +97,23 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) { function markLink(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) { let changed = false - if (!Link.isLoci(loci)) return false - for (const b of loci.links) { - const idx = structure.links.getBondIndex(b.aIndex, b.aUnit, b.bIndex, b.bUnit) - if (idx !== -1) { - if (apply(Interval.ofSingleton(idx))) changed = true + if (Link.isLoci(loci)) { + if (loci.structure !== structure) return false + for (const b of loci.links) { + const idx = structure.links.getBondIndex(b.aIndex, b.aUnit, b.bIndex, b.bUnit) + if (idx !== -1) { + if (apply(Interval.ofSingleton(idx))) changed = true + } + } + } else if (StructureElement.isLoci(loci)) { + if (loci.structure !== structure) return false + for (const e of loci.elements) { + OrderedSet.forEach(e.indices, v => { + const indices = structure.links.getBondIndices(v, e.unit) + for (let i = 0, il = indices.length; i < il; ++i) { + if (apply(Interval.ofSingleton(indices[i]))) changed = true + } + }) } } return changed diff --git a/src/mol-repr/structure/visual/intra-unit-link-cylinder.ts b/src/mol-repr/structure/visual/intra-unit-link-cylinder.ts index 5efdb9cbd6eb869ddf4813994aa003e405b6b85f..ca715a420b7bb10d8a496921c2828dc089e86159 100644 --- a/src/mol-repr/structure/visual/intra-unit-link-cylinder.ts +++ b/src/mol-repr/structure/visual/intra-unit-link-cylinder.ts @@ -8,11 +8,11 @@ import { Unit, Link, StructureElement, Structure } from 'mol-model/structure'; import { UnitsVisual } from '../representation'; import { VisualUpdateState } from '../../util'; -import { LinkCylinderProps, createLinkCylinderMesh, LinkIterator, LinkCylinderParams } from './util/link'; +import { createLinkCylinderMesh, LinkIterator, LinkCylinderParams } from './util/link'; import { Vec3 } from 'mol-math/linear-algebra'; import { Loci, EmptyLoci } from 'mol-model/loci'; import { UnitsMeshVisual, UnitsMeshParams, StructureGroup } from '../units-visual'; -import { Interval } from 'mol-data/int'; +import { Interval, OrderedSet } from 'mol-data/int'; import { BitFlags } from 'mol-util'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import { Mesh } from 'mol-geo/geometry/mesh/mesh'; @@ -29,7 +29,7 @@ async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, s const links = unit.links const { edgeCount, a, b, edgeProps, offset } = links const { order: _order, flags: _flags } = edgeProps - const { sizeFactor } = props + const { sizeFactor, sizeAspectRatio } = props if (!edgeCount) return Mesh.createEmpty(mesh) @@ -57,7 +57,7 @@ async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, s flags: (edgeIndex: number) => BitFlags.create(_flags[edgeIndex]), radius: (edgeIndex: number) => { location.element = elements[a[edgeIndex]] - return theme.size.size(location) * sizeFactor + return theme.size.size(location) * sizeFactor * sizeAspectRatio } } @@ -67,7 +67,8 @@ async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, s export const IntraUnitLinkParams = { ...UnitsMeshParams, ...LinkCylinderParams, - sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }), + sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }), + sizeAspectRatio: PD.Numeric(2/3, { min: 0, max: 3, step: 0.01 }), } export type IntraUnitLinkParams = typeof IntraUnitLinkParams @@ -78,10 +79,14 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkParams> { createLocationIterator: LinkIterator.fromGroup, getLoci: getLinkLoci, mark: markLink, - setUpdateState: (state: VisualUpdateState, newProps: LinkCylinderProps, currentProps: LinkCylinderProps) => { - if (newProps.linkScale !== currentProps.linkScale) state.createGeometry = true - if (newProps.linkSpacing !== currentProps.linkSpacing) state.createGeometry = true - if (newProps.radialSegments !== currentProps.radialSegments) state.createGeometry = true + setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IntraUnitLinkParams>, currentProps: PD.Values<IntraUnitLinkParams>) => { + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor || + newProps.sizeAspectRatio !== currentProps.sizeAspectRatio || + newProps.radialSegments !== currentProps.radialSegments || + newProps.linkScale !== currentProps.linkScale || + newProps.linkSpacing !== currentProps.linkSpacing + ) } }) } @@ -96,6 +101,10 @@ function getLinkLoci(pickingId: PickingId, structureGroup: StructureGroup, id: n Link.Location( unit, unit.links.a[groupId] as StructureElement.UnitIndex, unit, unit.links.b[groupId] as StructureElement.UnitIndex + ), + Link.Location( + unit, unit.links.b[groupId] as StructureElement.UnitIndex, + unit, unit.links.a[groupId] as StructureElement.UnitIndex ) ]) } @@ -105,18 +114,36 @@ function getLinkLoci(pickingId: PickingId, structureGroup: StructureGroup, id: n function markLink(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) { let changed = false - if (!Link.isLoci(loci)) return false - const { structure, group } = structureGroup - if (loci.structure !== structure) return false - const unit = group.units[0] - if (!Unit.isAtomic(unit)) return false - const groupCount = unit.links.edgeCount * 2 - for (const b of loci.links) { - const unitIdx = group.unitIndexMap.get(b.aUnit.id) - if (unitIdx !== undefined) { - const idx = unit.links.getDirectedEdgeIndex(b.aIndex, b.bIndex) - if (idx !== -1) { - if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true + if (Link.isLoci(loci)) { + const { structure, group } = structureGroup + if (loci.structure !== structure) return false + const unit = group.units[0] + if (!Unit.isAtomic(unit)) return false + const groupCount = unit.links.edgeCount * 2 + for (const b of loci.links) { + const unitIdx = group.unitIndexMap.get(b.aUnit.id) + if (unitIdx !== undefined) { + const idx = unit.links.getDirectedEdgeIndex(b.aIndex, b.bIndex) + if (idx !== -1) { + if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true + } + } + } + } else if (StructureElement.isLoci(loci)) { + const { structure, group } = structureGroup + if (loci.structure !== structure) return false + const unit = group.units[0] + if (!Unit.isAtomic(unit)) return false + const groupCount = unit.links.edgeCount * 2 + for (const e of loci.elements) { + const unitIdx = group.unitIndexMap.get(e.unit.id) + if (unitIdx !== undefined) { + const { offset } = unit.links + OrderedSet.forEach(e.indices, v => { + for (let t = offset[v], _t = offset[v + 1]; t < _t; t++) { + if (apply(Interval.ofSingleton(unitIdx * groupCount + t))) changed = true + } + }) } } } diff --git a/src/mol-repr/structure/visual/nucleotide-block-mesh.ts b/src/mol-repr/structure/visual/nucleotide-block-mesh.ts index e208a134b65a989b51c55554d008be367a750efd..8e5188611682aa9e0f533e9cde7da22311997263 100644 --- a/src/mol-repr/structure/visual/nucleotide-block-mesh.ts +++ b/src/mol-repr/structure/visual/nucleotide-block-mesh.ts @@ -19,6 +19,7 @@ import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder'; import { addCylinder } from 'mol-geo/geometry/mesh/builder/cylinder'; import { VisualContext } from 'mol-repr/representation'; import { Theme } from 'mol-theme/theme'; +import { VisualUpdateState } from 'mol-repr/util'; const p1 = Vec3.zero() const p2 = Vec3.zero() @@ -132,6 +133,10 @@ export function NucleotideBlockVisual(): UnitsVisual<NucleotideBlockParams> { createLocationIterator: NucleotideLocationIterator.fromGroup, getLoci: getNucleotideElementLoci, mark: markNucleotideElement, - setUpdateState: () => {} + setUpdateState: (state: VisualUpdateState, newProps: PD.Values<NucleotideBlockParams>, currentProps: PD.Values<NucleotideBlockParams>) => { + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor + ) + } }) } \ No newline at end of file diff --git a/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts b/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts index 62777f8bc72ef32cdaf5ffde8a70d455e8e58f37..2e27f6e0a8c4b76c988e1a568f57bc9600684b04 100644 --- a/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts +++ b/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts @@ -21,6 +21,7 @@ import { VisualContext } from 'mol-repr/representation'; import { Theme } from 'mol-theme/theme'; export const PolymerBackboneCylinderParams = { + sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }), radialSegments: PD.Numeric(16, { min: 3, max: 56, step: 1 }), } export const DefaultPolymerBackboneCylinderProps = PD.getDefaultValues(PolymerBackboneCylinderParams) @@ -30,7 +31,7 @@ async function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, const polymerElementCount = unit.polymerElements.length if (!polymerElementCount) return Mesh.createEmpty(mesh) - const { radialSegments } = props + const { radialSegments, sizeFactor } = props const vertexCountEstimate = radialSegments * 2 * polymerElementCount * 2 const builder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 10, mesh) @@ -48,11 +49,11 @@ async function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, pos(centerA.element, pA) pos(centerB.element, pB) - cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA) + cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA) * sizeFactor builder.setGroup(OrderedSet.indexOf(elements, centerA.element)) addCylinder(builder, pA, pB, 0.5, cylinderProps) - cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB) + cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB) * sizeFactor builder.setGroup(OrderedSet.indexOf(elements, centerB.element)) addCylinder(builder, pB, pA, 0.5, cylinderProps) @@ -80,7 +81,10 @@ export function PolymerBackboneVisual(): UnitsVisual<PolymerBackboneParams> { getLoci: getElementLoci, mark: markElement, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneParams>, currentProps: PD.Values<PolymerBackboneParams>) => { - state.createGeometry = newProps.radialSegments !== currentProps.radialSegments + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor || + newProps.radialSegments !== currentProps.radialSegments + ) } }) } \ No newline at end of file diff --git a/src/mol-repr/structure/visual/polymer-direction-wedge.ts b/src/mol-repr/structure/visual/polymer-direction-wedge.ts index 852fdbda7b48daa363d518672e07f5dc0a5f33fb..786ba2992b77deaf50e6ac687e74c8b93096a9c8 100644 --- a/src/mol-repr/structure/visual/polymer-direction-wedge.ts +++ b/src/mol-repr/structure/visual/polymer-direction-wedge.ts @@ -16,6 +16,7 @@ import { Mesh } from 'mol-geo/geometry/mesh/mesh'; import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder'; import { VisualContext } from 'mol-repr/representation'; import { Theme } from 'mol-theme/theme'; +import { VisualUpdateState } from 'mol-repr/util'; const t = Mat4.identity() const sVec = Vec3.zero() @@ -101,6 +102,10 @@ export function PolymerDirectionVisual(): UnitsVisual<PolymerDirectionParams> { createLocationIterator: PolymerLocationIterator.fromGroup, getLoci: getPolymerElementLoci, mark: markPolymerElement, - setUpdateState: () => {} + setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerDirectionParams>, currentProps: PD.Values<PolymerDirectionParams>) => { + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor + ) + } }) } \ No newline at end of file diff --git a/src/mol-repr/structure/visual/polymer-gap-cylinder.ts b/src/mol-repr/structure/visual/polymer-gap-cylinder.ts index 7b74e19ece9981d237feb2da18a622333dc3b4f1..818837ce18e3b010c18a63bc2c356afc32ec7446 100644 --- a/src/mol-repr/structure/visual/polymer-gap-cylinder.ts +++ b/src/mol-repr/structure/visual/polymer-gap-cylinder.ts @@ -96,7 +96,10 @@ export function PolymerGapVisual(): UnitsVisual<PolymerGapParams> { getLoci: getPolymerGapElementLoci, mark: markPolymerGapElement, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerGapParams>, currentProps: PD.Values<PolymerGapParams>) => { - state.createGeometry = newProps.radialSegments !== currentProps.radialSegments + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor || + newProps.radialSegments !== currentProps.radialSegments + ) } }) } \ No newline at end of file diff --git a/src/mol-repr/structure/visual/polymer-trace-mesh.ts b/src/mol-repr/structure/visual/polymer-trace-mesh.ts index e4eaad776ba22cc17dcc2e9cadf02528aac1920c..65d6b070eacfdc1d0b933048b3da26af57fee671 100644 --- a/src/mol-repr/structure/visual/polymer-trace-mesh.ts +++ b/src/mol-repr/structure/visual/polymer-trace-mesh.ts @@ -101,6 +101,7 @@ export function PolymerTraceVisual(): UnitsVisual<PolymerTraceParams> { mark: markPolymerElement, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerTraceParams>, currentProps: PD.Values<PolymerTraceParams>) => { state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor || newProps.linearSegments !== currentProps.linearSegments || newProps.radialSegments !== currentProps.radialSegments || newProps.aspectRatio !== currentProps.aspectRatio || diff --git a/src/mol-repr/structure/visual/util/polymer.ts b/src/mol-repr/structure/visual/util/polymer.ts index 79e24b21f6001e9420bebf7daa763586e8cd9823..65b9d7f3c189e3438ac62bbfbb2feacc8b588c33 100644 --- a/src/mol-repr/structure/visual/util/polymer.ts +++ b/src/mol-repr/structure/visual/util/polymer.ts @@ -6,11 +6,12 @@ import { Unit, ElementIndex, StructureElement, Link } from 'mol-model/structure'; import SortedRanges from 'mol-data/int/sorted-ranges'; -import { OrderedSet, Interval } from 'mol-data/int'; +import { OrderedSet, Interval, SortedArray } from 'mol-data/int'; import { EmptyLoci, Loci } from 'mol-model/loci'; import { LocationIterator } from 'mol-geo/util/location-iterator'; import { PickingId } from 'mol-geo/geometry/picking'; import { StructureGroup } from 'mol-repr/structure/units-visual'; +import { getElementIndexForAtomRole } from 'mol-model/structure/util'; export * from './polymer/backbone-iterator' export * from './polymer/gap-iterator' @@ -65,17 +66,22 @@ export namespace PolymerGapLocationIterator { } } +/** Return a Loci for the elements of a whole residue. */ export function getPolymerElementLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) { const { objectId, instanceId, groupId } = pickingId if (id === objectId) { const { structure, group } = structureGroup const unit = group.units[instanceId] - if (unit === undefined) { - console.log(id, { objectId, instanceId, groupId }, group.units) - } - const unitIndex = OrderedSet.indexOf(unit.elements, unit.polymerElements[groupId]) as StructureElement.UnitIndex - if (unitIndex !== -1) { - const indices = OrderedSet.ofSingleton(unitIndex) + const { elements, polymerElements, model } = unit + if (OrderedSet.indexOf(elements, polymerElements[groupId]) !== -1) { + const { index, offsets } = model.atomicHierarchy.residueAtomSegments + const rI = index[polymerElements[groupId]] + const _indices: number[] = [] + for (let i = offsets[rI], il = offsets[rI + 1]; i < il; ++i) { + const unitIndex = OrderedSet.indexOf(elements, i) + if (unitIndex !== -1) _indices.push(unitIndex) + } + const indices = OrderedSet.ofSortedArray<StructureElement.UnitIndex>(SortedArray.ofSortedArray(_indices)) return StructureElement.Loci(structure, [{ unit, indices }]) } } @@ -87,24 +93,25 @@ export function markPolymerElement(loci: Loci, structureGroup: StructureGroup, a if (!StructureElement.isLoci(loci)) return false const { structure, group } = structureGroup if (loci.structure !== structure) return false - const groupCount = group.units[0].polymerElements.length + const { polymerElements, model, elements } = group.units[0] + const { index, offsets } = model.atomicHierarchy.residueAtomSegments + const groupCount = polymerElements.length for (const e of loci.elements) { const unitIdx = group.unitIndexMap.get(e.unit.id) if (unitIdx !== undefined) { - if (Interval.is(e.indices)) { - const min = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.min(e.indices)]) - const max = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.max(e.indices)]) - if (min !== -1 && max !== -1) { - if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) changed = true + // TODO optimized implementation for intervals + OrderedSet.forEach(e.indices, v => { + const rI = index[elements[v]] + const unitIndexBeg = OrderedSet.indexOf(elements, offsets[rI]) + const unitIndexEnd = OrderedSet.indexOf(elements, offsets[rI + 1]) + const unitIndexInterval = Interval.ofBounds(unitIndexBeg, unitIndexEnd) + if(!OrderedSet.isSubset(e.indices, unitIndexInterval)) return + const eI = getElementIndexForAtomRole(model, rI, 'trace') + const idx = OrderedSet.indexOf(e.unit.polymerElements, eI) + if (idx !== -1) { + if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true } - } else { - for (let i = 0, _i = e.indices.length; i < _i; i++) { - const idx = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[e.indices[i]]) - if (idx !== -1) { - if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true - } - } - } + }) } } return changed