diff --git a/CHANGELOG.md b/CHANGELOG.md index c5667a79c9f84710336247e681f434786a578286..40b0426843bf46aaa4db84f662c14388229f14c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,35 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] + +## [v3.11.0] - 2022-07-04 + +- Add ``instanceGranularity`` option for marker, transparency, clipping, overpaint, substance data to save memory - CellPack extension tweaks - Use instancing to create DNA/RNA curves to save memory + - Enable ``instanceGranularity`` by default + - Add ``adjustStyle`` option to LoadCellPackModel action (stylized, no multi-sample, no far clipping, chain picking) +- Structure Superposition now respects pivot's coordinate system + +## [v3.10.2] - 2022-06-26 + +- Fix superfluous shader varying +- Improve use of gl_VertexID when possible + +## [v3.10.1] - 2022-06-26 + +- Fix groupCount when updating TextureMesh-based visuals + +## [v3.10.0] - 2022-06-24 + +- Add support for Glycam saccharide names +- Add ``PluginConfig.Viewport.ShowTrajectoryControls`` config option + +## [v3.9.1] - 2022-06-19 + +- Fix missing ``super.componentWillUnmount()`` calls (@simeonborko) +- Fix missing ``uGroupCount`` update for visuals +- Fix missing aromatic bond display ## [v3.9.0] - 2022-05-30 diff --git a/package-lock.json b/package-lock.json index 5e96935695f45d9e876484d5b3939a0e7cfb23b1..5acf9fba0766a64a8b2c37ec7bf200c239bf765c 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index acb6108265e2486282fabc6c1025a1f96367ddbb..ece9b6560bf7eecac8db21e68935222dcd031c0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "molstar", - "version": "3.10.2", + "version": "3.11.0", "description": "A comprehensive macromolecular library.", "homepage": "https://github.com/molstar/molstar#readme", "repository": { diff --git a/src/extensions/cellpack/model.ts b/src/extensions/cellpack/model.ts index 3bab461ff5be6c4db129491d5663a7ff3396ce50..a9babdc05b52181522f83db0b3569ed3a19a3b6a 100644 --- a/src/extensions/cellpack/model.ts +++ b/src/extensions/cellpack/model.ts @@ -415,6 +415,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p .apply(StructureFromAssemblies, undefined, { state: { isGhost: true } }) .commit({ revertOnError: true }); const membraneParams = { + ignoreLight: params.preset.adjustStyle, representation: params.preset.representation, }; await CellpackMembranePreset.apply(membrane, membraneParams, plugin); @@ -431,6 +432,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p .apply(StateTransforms.Model.StructureFromModel, props, { state: { isGhost: true } }) .commit({ revertOnError: true }); const membraneParams = { + ignoreLight: params.preset.adjustStyle, representation: params.preset.representation, }; await CellpackMembranePreset.apply(membrane, membraneParams, plugin); @@ -514,6 +516,7 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat const packingParams = { traceOnly: params.preset.traceOnly, + ignoreLight: params.preset.adjustStyle, representation: params.preset.representation, }; await CellpackPackingPreset.apply(packing, packingParams, plugin); @@ -565,6 +568,7 @@ const LoadCellPackModelParams = { ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }), preset: PD.Group({ traceOnly: PD.Boolean(false), + adjustStyle: PD.Boolean(true), representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation'] as const)) }, { isExpanded: true }) }; @@ -575,5 +579,32 @@ export const LoadCellPackModel = StateAction.build({ params: LoadCellPackModelParams, from: PSO.Root })(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => { + if (params.preset.adjustStyle) { + ctx.managers.interactivity.setProps({ granularity: 'chain' }); + ctx.canvas3d?.setProps({ + multiSample: { mode: 'off' }, + cameraClipping: { far: false }, + postprocessing: { + occlusion: { + name: 'on', + params: { + samples: 32, + radius: 8, + bias: 1, + blurKernelSize: 15, + resolutionScale: 1, + } + }, + outline: { + name: 'on', + params: { + scale: 1, + threshold: 0.33, + color: ColorNames.black, + } + } + } + }); + } await loadPackings(ctx, taskCtx, state, params); })); diff --git a/src/extensions/cellpack/preset.ts b/src/extensions/cellpack/preset.ts index 6fbfe6ca8c271a0ec3a2728e511c23d3dd2165db..12a0c256e976d5460817badc41e5a8089d90ecab 100644 --- a/src/extensions/cellpack/preset.ts +++ b/src/extensions/cellpack/preset.ts @@ -13,6 +13,7 @@ import { CellPackGenerateColorThemeProvider } from './color/generate'; export const CellpackPackingPresetParams = { traceOnly: PD.Boolean(true), + ignoreLight: PD.Boolean(false), representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'] as const)), }; export type CellpackPackingPresetParams = PD.ValuesFor<typeof CellpackPackingPresetParams> @@ -27,7 +28,9 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({ const reprProps = { ignoreHydrogens: true, - traceOnly: params.traceOnly + traceOnly: params.traceOnly, + instanceGranularity: true, + ignoreLight: params.ignoreLight, }; const components = { polymer: await presetStaticComponent(plugin, structureCell, 'polymer') @@ -57,6 +60,7 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({ // export const CellpackMembranePresetParams = { + ignoreLight: PD.Boolean(false), representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'] as const)), }; export type CellpackMembranePresetParams = PD.ValuesFor<typeof CellpackMembranePresetParams> @@ -71,6 +75,8 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({ const reprProps = { ignoreHydrogens: true, + instanceGranularity: true, + ignoreLight: params.ignoreLight, }; const components = { membrane: await presetStaticComponent(plugin, structureCell, 'all', { label: 'Membrane' }) diff --git a/src/mol-geo/geometry/base.ts b/src/mol-geo/geometry/base.ts index 953ebdf1a797c03ecf4a8942ebcc085c0d9fc7c0..ca2ba47d8f0a1a50b974cc56fffd31543e4568bd 100644 --- a/src/mol-geo/geometry/base.ts +++ b/src/mol-geo/geometry/base.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -83,6 +83,7 @@ export namespace BaseGeometry { quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }), material: Material.getParam(), clip: PD.Group(Clip.Params), + instanceGranularity: PD.Boolean(false, { description: 'Use instance granularity for marker, transparency, clipping, overpaint, substance data to save memory.' }), }; export type Params = typeof Params @@ -118,6 +119,8 @@ export namespace BaseGeometry { uClipObjectPosition: ValueCell.create(clip.objects.position), uClipObjectRotation: ValueCell.create(clip.objects.rotation), uClipObjectScale: ValueCell.create(clip.objects.scale), + + instanceGranularity: ValueCell.create(props.instanceGranularity), }; } @@ -135,6 +138,8 @@ export namespace BaseGeometry { ValueCell.update(values.uClipObjectPosition, clip.objects.position); ValueCell.update(values.uClipObjectRotation, clip.objects.rotation); ValueCell.update(values.uClipObjectScale, clip.objects.scale); + + ValueCell.updateIfChanged(values.instanceGranularity, props.instanceGranularity); } export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState { diff --git a/src/mol-geo/geometry/clipping-data.ts b/src/mol-geo/geometry/clipping-data.ts index 8c7b36336020266af3e11815c0edbe5338e2d491..e2c7720694d84eb954421feb1e06dd8043ac786b 100644 --- a/src/mol-geo/geometry/clipping-data.ts +++ b/src/mol-geo/geometry/clipping-data.ts @@ -9,10 +9,13 @@ import { Vec2 } from '../../mol-math/linear-algebra'; import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; import { Clipping } from '../../mol-theme/clipping'; +export type ClippingType = 'instance' | 'groupInstance'; + export type ClippingData = { tClipping: ValueCell<TextureImage<Uint8Array>> uClippingTexDim: ValueCell<Vec2> - dClipping: ValueCell<boolean>, + dClipping: ValueCell<boolean> + dClippingType: ValueCell<string> } export function applyClippingGroups(array: Uint8Array, start: number, end: number, groups: Clipping.Groups) { @@ -24,18 +27,20 @@ export function clearClipping(array: Uint8Array, start: number, end: number) { array.fill(0, start, end); } -export function createClipping(count: number, clippingData?: ClippingData): ClippingData { +export function createClipping(count: number, type: ClippingType, clippingData?: ClippingData): ClippingData { const clipping = createTextureImage(Math.max(1, count), 1, Uint8Array, clippingData && clippingData.tClipping.ref.value.array); if (clippingData) { ValueCell.update(clippingData.tClipping, clipping); ValueCell.update(clippingData.uClippingTexDim, Vec2.create(clipping.width, clipping.height)); ValueCell.updateIfChanged(clippingData.dClipping, count > 0); + ValueCell.updateIfChanged(clippingData.dClippingType, type); return clippingData; } else { return { tClipping: ValueCell.create(clipping), uClippingTexDim: ValueCell.create(Vec2.create(clipping.width, clipping.height)), dClipping: ValueCell.create(count > 0), + dClippingType: ValueCell.create(type), }; } } @@ -52,6 +57,7 @@ export function createEmptyClipping(clippingData?: ClippingData): ClippingData { tClipping: ValueCell.create(emptyClippingTexture), uClippingTexDim: ValueCell.create(Vec2.create(1, 1)), dClipping: ValueCell.create(false), + dClippingType: ValueCell.create('groupInstance'), }; } } \ No newline at end of file diff --git a/src/mol-geo/geometry/cylinders/cylinders.ts b/src/mol-geo/geometry/cylinders/cylinders.ts index 341bc72fcec7344096c4bed5d31f318089315557..99929acd159c5d62398dd813a2892a28d5a7037f 100644 --- a/src/mol-geo/geometry/cylinders/cylinders.ts +++ b/src/mol-geo/geometry/cylinders/cylinders.ts @@ -201,7 +201,9 @@ export namespace Cylinders { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, theme.size); - const marker = createMarkers(instanceCount * groupCount); + const marker = props.instanceGranularity + ? createMarkers(instanceCount, 'instance') + : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); const material = createEmptySubstance(); diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index f0cf9cf16321f9919797eba8b5bdee27bf5f7c37..a298d597e4bf3bae58a710702756399d563d98a4 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/src/mol-geo/geometry/direct-volume/direct-volume.ts @@ -211,7 +211,9 @@ export namespace DirectVolume { const positionIt = Utils.createPositionIterator(directVolume, transform); const color = createColors(locationIt, positionIt, theme.color); - const marker = createMarkers(instanceCount * groupCount); + const marker = props.instanceGranularity + ? createMarkers(instanceCount, 'instance') + : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); const material = createEmptySubstance(); diff --git a/src/mol-geo/geometry/image/image.ts b/src/mol-geo/geometry/image/image.ts index e479434faca3396a1c7064512f3638628da93277..cd4e1992272a9324bae51ebe851d94c6569df819 100644 --- a/src/mol-geo/geometry/image/image.ts +++ b/src/mol-geo/geometry/image/image.ts @@ -143,7 +143,9 @@ namespace Image { const positionIt = Utils.createPositionIterator(image, transform); const color = createColors(locationIt, positionIt, theme.color); - const marker = createMarkers(instanceCount * groupCount); + const marker = props.instanceGranularity + ? createMarkers(instanceCount, 'instance') + : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); const material = createEmptySubstance(); diff --git a/src/mol-geo/geometry/lines/lines.ts b/src/mol-geo/geometry/lines/lines.ts index 5452ac7611d4698522c1cf08c31ccddd1272f2c3..ecf1aa21b50583167562dd3c4feac7f8e2badf04 100644 --- a/src/mol-geo/geometry/lines/lines.ts +++ b/src/mol-geo/geometry/lines/lines.ts @@ -208,7 +208,9 @@ export namespace Lines { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, theme.size); - const marker = createMarkers(instanceCount * groupCount); + const marker = props.instanceGranularity + ? createMarkers(instanceCount, 'instance') + : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); const material = createEmptySubstance(); diff --git a/src/mol-geo/geometry/marker-data.ts b/src/mol-geo/geometry/marker-data.ts index a159eb3a4cbe54a7b0454a1191c50321df0bab55..58356b0b298c1295be1037a180d1753a678306fc 100644 --- a/src/mol-geo/geometry/marker-data.ts +++ b/src/mol-geo/geometry/marker-data.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -8,12 +8,15 @@ import { ValueCell } from '../../mol-util/value-cell'; import { Vec2 } from '../../mol-math/linear-algebra'; import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; +export type MarkerType = 'instance' | 'groupInstance'; + export type MarkerData = { - uMarker: ValueCell<number>, + uMarker: ValueCell<number> tMarker: ValueCell<TextureImage<Uint8Array>> uMarkerTexDim: ValueCell<Vec2> markerAverage: ValueCell<number> markerStatus: ValueCell<number> + dMarkerType: ValueCell<string> } const MarkerCountLut = new Uint8Array(0x0303 + 1); @@ -64,7 +67,7 @@ export function getMarkersAverage(array: Uint8Array, count: number): number { return sum / count; } -export function createMarkers(count: number, markerData?: MarkerData): MarkerData { +export function createMarkers(count: number, type: MarkerType, markerData?: MarkerData): MarkerData { const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array); const average = getMarkersAverage(markers.array, count); const status = average === 0 ? 0 : -1; @@ -74,6 +77,7 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat ValueCell.update(markerData.uMarkerTexDim, Vec2.create(markers.width, markers.height)); ValueCell.updateIfChanged(markerData.markerAverage, average); ValueCell.updateIfChanged(markerData.markerStatus, status); + ValueCell.updateIfChanged(markerData.dMarkerType, type); return markerData; } else { return { @@ -82,6 +86,7 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat uMarkerTexDim: ValueCell.create(Vec2.create(markers.width, markers.height)), markerAverage: ValueCell.create(average), markerStatus: ValueCell.create(status), + dMarkerType: ValueCell.create(type), }; } } @@ -102,6 +107,7 @@ export function createEmptyMarkers(markerData?: MarkerData): MarkerData { uMarkerTexDim: ValueCell.create(Vec2.create(1, 1)), markerAverage: ValueCell.create(0), markerStatus: ValueCell.create(0), + dMarkerType: ValueCell.create('groupInstance'), }; } } \ No newline at end of file diff --git a/src/mol-geo/geometry/mesh/mesh.ts b/src/mol-geo/geometry/mesh/mesh.ts index 20daa221d1d228c6c79cd757de1948fffc6041de..29b08ba3a935cac0f9a95f70da6e7bcef23f1ad5 100644 --- a/src/mol-geo/geometry/mesh/mesh.ts +++ b/src/mol-geo/geometry/mesh/mesh.ts @@ -666,7 +666,9 @@ export namespace Mesh { const positionIt = createPositionIterator(mesh, transform); const color = createColors(locationIt, positionIt, theme.color); - const marker = createMarkers(instanceCount * groupCount); + const marker = props.instanceGranularity + ? createMarkers(instanceCount, 'instance') + : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); const material = createEmptySubstance(); diff --git a/src/mol-geo/geometry/overpaint-data.ts b/src/mol-geo/geometry/overpaint-data.ts index 70ab4e67a41adcaad028f264b8ff98ee1ce52d9f..3c90b2bf0d46319461118aa639b47d0fd03a71ad 100644 --- a/src/mol-geo/geometry/overpaint-data.ts +++ b/src/mol-geo/geometry/overpaint-data.ts @@ -10,6 +10,8 @@ import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; import { Color } from '../../mol-util/color'; import { createNullTexture, Texture } from '../../mol-gl/webgl/texture'; +export type OverpaintType = 'instance' | 'groupInstance' | 'volumeInstance'; + export type OverpaintData = { tOverpaint: ValueCell<TextureImage<Uint8Array>> uOverpaintTexDim: ValueCell<Vec2> @@ -34,12 +36,13 @@ export function clearOverpaint(array: Uint8Array, start: number, end: number) { return true; } -export function createOverpaint(count: number, overpaintData?: OverpaintData): OverpaintData { +export function createOverpaint(count: number, type: OverpaintType, overpaintData?: OverpaintData): OverpaintData { const overpaint = createTextureImage(Math.max(1, count), 4, Uint8Array, overpaintData && overpaintData.tOverpaint.ref.value.array); if (overpaintData) { ValueCell.update(overpaintData.tOverpaint, overpaint); ValueCell.update(overpaintData.uOverpaintTexDim, Vec2.create(overpaint.width, overpaint.height)); ValueCell.updateIfChanged(overpaintData.dOverpaint, count > 0); + ValueCell.updateIfChanged(overpaintData.dOverpaintType, type); return overpaintData; } else { return { @@ -50,7 +53,7 @@ export function createOverpaint(count: number, overpaintData?: OverpaintData): O tOverpaintGrid: ValueCell.create(createNullTexture()), uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)), uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)), - dOverpaintType: ValueCell.create('groupInstance'), + dOverpaintType: ValueCell.create(type), }; } } diff --git a/src/mol-geo/geometry/points/points.ts b/src/mol-geo/geometry/points/points.ts index 6ff2e72b39eb3e9117046939bfe46b5b7aa90f28..e6c2fbd2defb792b0c6e58b3124c024085677de4 100644 --- a/src/mol-geo/geometry/points/points.ts +++ b/src/mol-geo/geometry/points/points.ts @@ -170,7 +170,9 @@ export namespace Points { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, theme.size); - const marker = createMarkers(instanceCount * groupCount); + const marker = props.instanceGranularity + ? createMarkers(instanceCount, 'instance') + : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); const material = createEmptySubstance(); diff --git a/src/mol-geo/geometry/spheres/spheres.ts b/src/mol-geo/geometry/spheres/spheres.ts index 7b442ca910a94e760bf470321fb404d4a65573f0..b7be6f095ad44f85ee525fc7bd7af1c3f82c6a77 100644 --- a/src/mol-geo/geometry/spheres/spheres.ts +++ b/src/mol-geo/geometry/spheres/spheres.ts @@ -171,7 +171,9 @@ export namespace Spheres { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, theme.size); - const marker = createMarkers(instanceCount * groupCount); + const marker = props.instanceGranularity + ? createMarkers(instanceCount, 'instance') + : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); const material = createEmptySubstance(); diff --git a/src/mol-geo/geometry/substance-data.ts b/src/mol-geo/geometry/substance-data.ts index dfd74ad95332a3b941d1ccd9439fb0891325d393..554d7e9f40fdcedc413249f5566decb70ad3e56c 100644 --- a/src/mol-geo/geometry/substance-data.ts +++ b/src/mol-geo/geometry/substance-data.ts @@ -10,6 +10,8 @@ import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; import { createNullTexture, Texture } from '../../mol-gl/webgl/texture'; import { Material } from '../../mol-util/material'; +export type SubstanceType = 'instance' | 'groupInstance' | 'volumeInstance'; + export type SubstanceData = { tSubstance: ValueCell<TextureImage<Uint8Array>> uSubstanceTexDim: ValueCell<Vec2> @@ -34,12 +36,13 @@ export function clearSubstance(array: Uint8Array, start: number, end: number) { return true; } -export function createSubstance(count: number, substanceData?: SubstanceData): SubstanceData { +export function createSubstance(count: number, type: SubstanceType, substanceData?: SubstanceData): SubstanceData { const substance = createTextureImage(Math.max(1, count), 4, Uint8Array, substanceData && substanceData.tSubstance.ref.value.array); if (substanceData) { ValueCell.update(substanceData.tSubstance, substance); ValueCell.update(substanceData.uSubstanceTexDim, Vec2.create(substance.width, substance.height)); ValueCell.updateIfChanged(substanceData.dSubstance, count > 0); + ValueCell.updateIfChanged(substanceData.dSubstanceType, type); return substanceData; } else { return { @@ -50,7 +53,7 @@ export function createSubstance(count: number, substanceData?: SubstanceData): S tSubstanceGrid: ValueCell.create(createNullTexture()), uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)), uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)), - dSubstanceType: ValueCell.create('groupInstance'), + dSubstanceType: ValueCell.create(type), }; } } diff --git a/src/mol-geo/geometry/text/text.ts b/src/mol-geo/geometry/text/text.ts index 85a11f973e7aa51b15f666e788a29ddadc102847..c68cae99e1fb883dadc2828317a265461c6158fa 100644 --- a/src/mol-geo/geometry/text/text.ts +++ b/src/mol-geo/geometry/text/text.ts @@ -211,7 +211,9 @@ export namespace Text { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, theme.size); - const marker = createMarkers(instanceCount * groupCount); + const marker = props.instanceGranularity + ? createMarkers(instanceCount, 'instance') + : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); const substance = createEmptySubstance(); diff --git a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts index ff2d989b517b6846e1c0f760d266a8d8c17c545d..8c941543972963b7e949c500ee4c0d5c41ab347e 100644 --- a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts +++ b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts @@ -137,7 +137,9 @@ export namespace TextureMesh { const positionIt = Utils.createPositionIterator(textureMesh, transform); const color = createColors(locationIt, positionIt, theme.color); - const marker = createMarkers(instanceCount * groupCount); + const marker = props.instanceGranularity + ? createMarkers(instanceCount, 'instance') + : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); const substance = createEmptySubstance(); diff --git a/src/mol-geo/geometry/transparency-data.ts b/src/mol-geo/geometry/transparency-data.ts index fb9df06819e17d041d0a45298d6ba15a0f836822..a619443a7b99c42efc6db9e6e43f6e6abf6e4f36 100644 --- a/src/mol-geo/geometry/transparency-data.ts +++ b/src/mol-geo/geometry/transparency-data.ts @@ -9,6 +9,8 @@ import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra'; import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; import { createNullTexture, Texture } from '../../mol-gl/webgl/texture'; +export type TransparencyType = 'instance' | 'groupInstance' | 'volumeInstance'; + export type TransparencyData = { tTransparency: ValueCell<TextureImage<Uint8Array>> uTransparencyTexDim: ValueCell<Vec2> @@ -41,13 +43,14 @@ export function clearTransparency(array: Uint8Array, start: number, end: number) array.fill(0, start, end); } -export function createTransparency(count: number, transparencyData?: TransparencyData): TransparencyData { +export function createTransparency(count: number, type: TransparencyType, transparencyData?: TransparencyData): TransparencyData { const transparency = createTextureImage(Math.max(1, count), 1, Uint8Array, transparencyData && transparencyData.tTransparency.ref.value.array); if (transparencyData) { ValueCell.update(transparencyData.tTransparency, transparency); ValueCell.update(transparencyData.uTransparencyTexDim, Vec2.create(transparency.width, transparency.height)); ValueCell.updateIfChanged(transparencyData.dTransparency, count > 0); ValueCell.updateIfChanged(transparencyData.transparencyAverage, getTransparencyAverage(transparency.array, count)); + ValueCell.updateIfChanged(transparencyData.dTransparencyType, type); return transparencyData; } else { return { @@ -59,7 +62,7 @@ export function createTransparency(count: number, transparencyData?: Transparenc tTransparencyGrid: ValueCell.create(createNullTexture()), uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)), uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)), - dTransparencyType: ValueCell.create('groupInstance'), + dTransparencyType: ValueCell.create(type), }; } } diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 9881dc80730278dcfc5ec501df26c77362135e99..6880287cbf44de7927555000c09b8a04b38487cf 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -205,6 +205,7 @@ export const MarkerSchema = { tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), markerAverage: ValueSpec('number'), markerStatus: ValueSpec('number'), + dMarkerType: DefineSpec('string', ['instance', 'groupInstance']), } as const; export type MarkerSchema = typeof MarkerSchema export type MarkerValues = Values<MarkerSchema> @@ -217,7 +218,7 @@ export const OverpaintSchema = { uOverpaintGridDim: UniformSpec('v3'), uOverpaintGridTransform: UniformSpec('v4'), tOverpaintGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), - dOverpaintType: DefineSpec('string', ['groupInstance', 'volumeInstance']), + dOverpaintType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']), } as const; export type OverpaintSchema = typeof OverpaintSchema export type OverpaintValues = Values<OverpaintSchema> @@ -231,7 +232,7 @@ export const TransparencySchema = { uTransparencyGridDim: UniformSpec('v3'), uTransparencyGridTransform: UniformSpec('v4'), tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'), - dTransparencyType: DefineSpec('string', ['groupInstance', 'volumeInstance']), + dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']), } as const; export type TransparencySchema = typeof TransparencySchema export type TransparencyValues = Values<TransparencySchema> @@ -244,7 +245,7 @@ export const SubstanceSchema = { uSubstanceGridDim: UniformSpec('v3'), uSubstanceGridTransform: UniformSpec('v4'), tSubstanceGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), - dSubstanceType: DefineSpec('string', ['groupInstance', 'volumeInstance']), + dSubstanceType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']), } as const; export type SubstanceSchema = typeof SubstanceSchema export type SubstanceValues = Values<SubstanceSchema> @@ -253,6 +254,7 @@ export const ClippingSchema = { uClippingTexDim: UniformSpec('v2'), tClipping: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dClipping: DefineSpec('boolean'), + dClippingType: DefineSpec('string', ['instance', 'groupInstance']), } as const; export type ClippingSchema = typeof ClippingSchema export type ClippingValues = Values<ClippingSchema> @@ -311,6 +313,8 @@ export const BaseSchema = { extraTransform: ValueSpec('float32'), /** denotes reflection in transform */ hasReflection: ValueSpec('boolean'), + /** use instance granularity for marker, transparency, clipping, overpaint, substance */ + instanceGranularity: ValueSpec('boolean'), /** bounding sphere taking aTransform into account and encompases all instances */ boundingSphere: ValueSpec('sphere'), diff --git a/src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts b/src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts index 3d48b7ecb777bcd7e833d64399ea09edf0f700ee..88ed1b431092d192cddba25d62a90f3fb9de8857 100644 --- a/src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts @@ -1,5 +1,9 @@ export const assign_clipping_varying = ` #if dClipObjectCount != 0 && defined(dClipping) - vClipping = readFromTexture(tClipping, aInstance * float(uGroupCount) + group, uClippingTexDim).a; + #if defined(dClippingType_instance) + vClipping = readFromTexture(tClipping, aInstance, uClippingTexDim).a; + #elif defined(dMarkerType_groupInstance) + vClipping = readFromTexture(tClipping, aInstance * float(uGroupCount) + group, uClippingTexDim).a; + #endif #endif `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts b/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts index fa8ea04fb29668125a0ba59fb1a6fef5214bcec7..7be1d4ff06dad0026c22c578131295e7ea8f37b7 100644 --- a/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts @@ -25,7 +25,9 @@ export const assign_color_varying = ` #endif #ifdef dOverpaint - #if defined(dOverpaintType_groupInstance) + #if defined(dOverpaintType_instance) + vOverpaint = readFromTexture(tOverpaint, aInstance, uOverpaintTexDim); + #elif defined(dOverpaintType_groupInstance) vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim); #elif defined(dOverpaintType_vertexInstance) vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + VertexID, uOverpaintTexDim); @@ -43,7 +45,9 @@ export const assign_color_varying = ` #endif #ifdef dSubstance - #if defined(dSubstanceType_groupInstance) + #if defined(dSubstanceType_instance) + vSubstance = readFromTexture(tSubstance, aInstance, uSubstanceTexDim); + #elif defined(dSubstanceType_groupInstance) vSubstance = readFromTexture(tSubstance, aInstance * float(uGroupCount) + group, uSubstanceTexDim); #elif defined(dSubstanceType_vertexInstance) vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + VertexID, uSubstanceTexDim); @@ -72,7 +76,9 @@ export const assign_color_varying = ` #endif #ifdef dTransparency - #if defined(dTransparencyType_groupInstance) + #if defined(dTransparencyType_instance) + vTransparency = readFromTexture(tTransparency, aInstance, uTransparencyTexDim).a; + #elif defined(dTransparencyType_groupInstance) vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a; #elif defined(dTransparencyType_vertexInstance) vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + VertexID, uTransparencyTexDim).a; diff --git a/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts b/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts index 9509ee52e63cc7fe1ab6f7d14ddcffcb279ad8dc..361ff36decaa432001944ba7a796b11c99203e02 100644 --- a/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts @@ -1,5 +1,9 @@ export const assign_marker_varying = ` #if defined(dRenderVariant_color) || defined(dRenderVariant_marking) - vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a; + #if defined(dMarkerType_instance) + vMarker = readFromTexture(tMarker, aInstance, uMarkerTexDim).a; + #elif defined(dMarkerType_groupInstance) + vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a; + #endif #endif `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts index b57c66a9ea54abae608edb81059e0db31a88eeb3..5459d2fc31658b1eb257bb67852009c2692bea8d 100644 --- a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts +++ b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts @@ -28,7 +28,7 @@ uniform float uBumpiness; #endif #ifdef dOverpaint - #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance) + #if defined(dOverpaintType_instance) || defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance) varying vec4 vOverpaint; uniform vec2 uOverpaintTexDim; uniform sampler2D tOverpaint; @@ -42,7 +42,7 @@ uniform float uBumpiness; #endif #ifdef dSubstance - #if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance) + #if defined(dSubstanceType_instance) || defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance) varying vec4 vSubstance; uniform vec2 uSubstanceTexDim; uniform sampler2D tSubstance; @@ -75,7 +75,7 @@ uniform float uBumpiness; #endif #ifdef dTransparency - #if defined(dTransparencyType_groupInstance) || defined(dTransparencyType_vertexInstance) + #if defined(dTransparencyType_instance) || defined(dTransparencyType_groupInstance) || defined(dTransparencyType_vertexInstance) varying float vTransparency; uniform vec2 uTransparencyTexDim; uniform sampler2D tTransparency; diff --git a/src/mol-plugin-state/manager/structure/hierarchy.ts b/src/mol-plugin-state/manager/structure/hierarchy.ts index e9cc03ec62c2a52764b14914e7c5e01b5941adc1..b94db5381cad3fb877e1e12a33af4484d065a405 100644 --- a/src/mol-plugin-state/manager/structure/hierarchy.ts +++ b/src/mol-plugin-state/manager/structure/hierarchy.ts @@ -5,6 +5,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import { Structure } from '../../../mol-model/structure'; import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state'; import { PluginCommands } from '../../../mol-plugin/commands'; import { PluginContext } from '../../../mol-plugin/context'; @@ -12,6 +13,7 @@ import { StateTransform, StateTree } from '../../../mol-state'; import { SetUtils } from '../../../mol-util/set'; import { TrajectoryHierarchyPresetProvider } from '../../builder/structure/hierarchy-preset'; import { PluginComponent } from '../../component'; +import { PluginStateObject } from '../../objects'; import { buildStructureHierarchy, StructureHierarchyRef, ModelRef, StructureComponentRef, StructureHierarchy, StructureRef, TrajectoryRef } from './hierarchy-state'; export class StructureHierarchyManager extends PluginComponent { @@ -79,6 +81,18 @@ export class StructureHierarchyManager extends PluginComponent { return ret; } + findStructure(structure: Structure | undefined): StructureRef | undefined { + if (!structure) return undefined; + + const parent = this.plugin.helpers.substructureParent.get(structure); + if (!parent) return undefined; + + const root = this.plugin.state.data.selectQ(q => q.byValue(parent).rootOfType(PluginStateObject.Molecule.Structure))[0]; + if (!root) return undefined; + + return this.behaviors.selection.value.structures.find(s => s.cell === root); + } + private syncCurrent<T extends StructureHierarchyRef>(all: ReadonlyArray<T>, added: Set<StateTransform.Ref>): T[] { const current = this.seletionSet; const newCurrent: T[] = []; diff --git a/src/mol-plugin-ui/structure/superposition.tsx b/src/mol-plugin-ui/structure/superposition.tsx index 97216a7af2b81f29880ff3172716e2f7bbbc4e87..dc59353b887950753d040b96683d054dc247790d 100644 --- a/src/mol-plugin-ui/structure/superposition.tsx +++ b/src/mol-plugin-ui/structure/superposition.tsx @@ -5,25 +5,26 @@ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org> */ -import { CollapsableControls, PurePluginUIComponent } from '../base'; -import { Icon, ArrowUpwardSvg, ArrowDownwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, TuneSvg, SuperposeAtomsSvg, SuperposeChainsSvg, SuperpositionSvg } from '../controls/icons'; -import { Button, ToggleButton, IconButton } from '../controls/common'; -import { StructureElement, StructureSelection, QueryContext, Structure, StructureProperties } from '../../mol-model/structure'; +import { SymmetryOperator } from '../../mol-math/geometry'; import { Mat4 } from '../../mol-math/linear-algebra'; -import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { StateObjectRef, StateObjectCell, StateSelection } from '../../mol-state'; -import { StateTransforms } from '../../mol-plugin-state/transforms'; -import { PluginStateObject } from '../../mol-plugin-state/objects'; +import { SIFTSMapping } from '../../mol-model-props/sequence/sifts-mapping'; +import { QueryContext, Structure, StructureElement, StructureProperties, StructureSelection } from '../../mol-model/structure'; import { alignAndSuperpose, superpose } from '../../mol-model/structure/structure/util/superposition'; +import { alignAndSuperposeWithSIFTSMapping } from '../../mol-model/structure/structure/util/superposition-sifts-mapping'; import { StructureSelectionQueries } from '../../mol-plugin-state/helpers/structure-selection-query'; -import { structureElementStatsLabel, elementLabel } from '../../mol-theme/label'; -import { ParameterControls } from '../controls/parameters'; -import { stripTags } from '../../mol-util/string'; import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection'; -import { ToggleSelectionModeButton } from './selection'; -import { alignAndSuperposeWithSIFTSMapping } from '../../mol-model/structure/structure/util/superposition-sifts-mapping'; +import { PluginStateObject } from '../../mol-plugin-state/objects'; +import { StateTransforms } from '../../mol-plugin-state/transforms'; import { PluginCommands } from '../../mol-plugin/commands'; -import { SIFTSMapping } from '../../mol-model-props/sequence/sifts-mapping'; +import { StateObjectCell, StateObjectRef } from '../../mol-state'; +import { elementLabel, structureElementStatsLabel } from '../../mol-theme/label'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { stripTags } from '../../mol-util/string'; +import { CollapsableControls, PurePluginUIComponent } from '../base'; +import { Button, IconButton, ToggleButton } from '../controls/common'; +import { ArrowDownwardSvg, ArrowUpwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, SuperposeAtomsSvg, SuperposeChainsSvg, SuperpositionSvg, TuneSvg } from '../controls/icons'; +import { ParameterControls } from '../controls/parameters'; +import { ToggleSelectionModeButton } from './selection'; export class StructureSuperpositionControls extends CollapsableControls { defaultState() { @@ -104,19 +105,21 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit return this.plugin.managers.structure.selection; } - async transform(s: StateObjectRef<PluginStateObject.Molecule.Structure>, matrix: Mat4) { + async transform(s: StateObjectRef<PluginStateObject.Molecule.Structure>, matrix: Mat4, coordinateSystem?: SymmetryOperator) { const r = StateObjectRef.resolveAndCheck(this.plugin.state.data, s); if (!r) return; - // TODO should find any TransformStructureConformation decorator instance - const o = StateSelection.findTagInSubtree(this.plugin.state.data.tree, r.transform.ref, SuperpositionTag); + const o = this.plugin.state.data.selectQ(q => q.byRef(r.transform.ref).subtree().withTransformer(StateTransforms.Model.TransformStructureConformation))[0]; + + const transform = coordinateSystem && !Mat4.isIdentity(coordinateSystem.matrix) + ? Mat4.mul(Mat4(), coordinateSystem.matrix, matrix) + : matrix; const params = { transform: { name: 'matrix' as const, - params: { data: matrix, transpose: false } + params: { data: transform, transpose: false } } }; - // TODO add .insertOrUpdate to StateBuilder? const b = o ? this.plugin.state.data.build().to(o).update(params) : this.plugin.state.data.build().to(s) @@ -124,19 +127,24 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit await this.plugin.runTask(this.plugin.state.data.updateTree(b)); } + private getRootStructure(s: Structure) { + const parent = this.plugin.helpers.substructureParent.get(s)!; + return this.plugin.state.data.selectQ(q => q.byValue(parent).rootOfType(PluginStateObject.Molecule.Structure))[0].obj?.data!; + } + superposeChains = async () => { const { query } = this.state.options.traceOnly ? StructureSelectionQueries.trace : StructureSelectionQueries.polymer; const entries = this.chainEntries; - const locis = entries.map((e, i) => { + const locis = entries.map(e => { const s = StructureElement.Loci.toStructure(e.loci); const loci = StructureSelection.toLociWithSourceUnits(query(new QueryContext(s))); - return StructureElement.Loci.remap(loci, i === 0 - ? this.plugin.helpers.substructureParent.get(e.loci.structure.root)!.obj!.data - : loci.structure.root - ); + return StructureElement.Loci.remap(loci, this.getRootStructure(e.loci.structure)); }); + const pivot = this.plugin.managers.structure.hierarchy.findStructure(locis[0]?.structure); + const coordinateSystem = pivot?.transform?.cell.obj?.data.coordinateSystem; + const transforms = this.state.options.alignSequences ? alignAndSuperpose(locis) : superpose(locis); @@ -145,7 +153,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit for (let i = 1, il = locis.length; i < il; ++i) { const eB = entries[i]; const { bTransform, rmsd } = transforms[i - 1]; - await this.transform(eB.cell, bTransform); + await this.transform(eB.cell, bTransform, coordinateSystem); const labelA = stripTags(eA.label); const labelB = stripTags(eB.label); this.plugin.log.info(`Superposed [${labelA}] and [${labelB}] with RMSD ${rmsd.toFixed(2)}.`); @@ -156,19 +164,19 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit superposeAtoms = async () => { const entries = this.atomEntries; - const atomLocis = entries.map((e, i) => { - return StructureElement.Loci.remap(e.loci, i === 0 - ? this.plugin.helpers.substructureParent.get(e.loci.structure.root)!.obj!.data - : e.loci.structure.root - ); + const atomLocis = entries.map(e => { + return StructureElement.Loci.remap(e.loci, this.getRootStructure(e.loci.structure)); }); const transforms = superpose(atomLocis); + const pivot = this.plugin.managers.structure.hierarchy.findStructure(atomLocis[0]?.structure); + const coordinateSystem = pivot?.transform?.cell.obj?.data.coordinateSystem; + const eA = entries[0]; for (let i = 1, il = atomLocis.length; i < il; ++i) { const eB = entries[i]; const { bTransform, rmsd } = transforms[i - 1]; - await this.transform(eB.cell, bTransform); + await this.transform(eB.cell, bTransform, coordinateSystem); const labelA = stripTags(eA.label); const labelB = stripTags(eB.label); const count = entries[i].atoms.length; @@ -184,10 +192,12 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit const structures = input.map(s => s.cell.obj?.data!); const { entries, failedPairs, zeroOverlapPairs } = alignAndSuperposeWithSIFTSMapping(structures, { traceOnly }); + const coordinateSystem = input[0]?.transform?.cell.obj?.data.coordinateSystem; + let rmsd = 0; for (const xform of entries) { - await this.transform(input[xform.other].cell, xform.transform.bTransform); + await this.transform(input[xform.other].cell, xform.transform.bTransform, coordinateSystem); rmsd += xform.transform.rmsd; } diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index 8571dc7353e2c74e326c338c708548133a2d3278..803199b180ff825601d3c1c88b2d5c69eef1f6c7 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -78,6 +78,10 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa console.warn('unexpected state'); } + if (props.instanceGranularity !== currentProps.instanceGranularity) { + updateState.updateTransform = true; + } + if (updateState.updateTransform) { updateState.updateColor = true; updateState.updateSize = true; @@ -125,7 +129,11 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa Shape.createTransform(_shape.transforms, _renderObject.values); locationIt = Shape.groupIterator(_shape); const { instanceCount, groupCount } = locationIt; - createMarkers(instanceCount * groupCount, _renderObject.values); + if (props.instanceGranularity) { + createMarkers(instanceCount, 'instance', _renderObject.values); + } else { + createMarkers(instanceCount * groupCount, 'groupInstance', _renderObject.values); + } } if (updateState.createGeometry) { @@ -167,11 +175,30 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa }); } + function eachInstance(loci: Loci, shape: Shape, apply: (interval: Interval) => boolean) { + let changed = false; + if (!ShapeGroup.isLoci(loci)) return false; + if (ShapeGroup.isLociEmpty(loci)) return false; + if (loci.shape !== shape) return false; + for (const g of loci.groups) { + if (apply(Interval.ofSingleton(g.instance))) changed = true; + } + return changed; + } + function lociApply(loci: Loci, apply: (interval: Interval) => boolean) { if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) { - return apply(Interval.ofBounds(0, _shape.groupCount * _shape.transforms.length)); + if (currentProps.instanceGranularity) { + return apply(Interval.ofBounds(0, _shape.transforms.length)); + } else { + return apply(Interval.ofBounds(0, _shape.groupCount * _shape.transforms.length)); + } } else { - return eachShapeGroup(loci, _shape, apply); + if (currentProps.instanceGranularity) { + return eachInstance(loci, _shape, apply); + } else { + return eachShapeGroup(loci, _shape, apply); + } } } diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index e1b13148d48555b8d5bf670e40839783d80952d4..71f5314e921092800bf3a916e11bf2f480776687 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/src/mol-repr/structure/complex-visual.ts @@ -6,7 +6,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { Visual, VisualContext } from '../visual'; -import { Structure, StructureElement } from '../../mol-model/structure'; +import { Bond, Structure, StructureElement } from '../../mol-model/structure'; import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry'; import { LocationIterator } from '../../mol-geo/util/location-iterator'; import { Theme } from '../../mol-theme/theme'; @@ -126,6 +126,10 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge updateState.createGeometry = true; } + if (newProps.instanceGranularity !== currentProps.instanceGranularity) { + updateState.updateTransform = true; + } + if (updateState.updateSize && !('uSize' in renderObject.values)) { updateState.createGeometry = true; } @@ -154,7 +158,11 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge // console.log('update transform') locationIt = createLocationIterator(newStructure); const { instanceCount, groupCount } = locationIt; - createMarkers(instanceCount * groupCount, renderObject.values); + if (newProps.instanceGranularity) { + createMarkers(instanceCount, 'instance', renderObject.values); + } else { + createMarkers(instanceCount * groupCount, 'groupInstance', renderObject.values); + } } if (updateState.createGeometry) { @@ -205,11 +213,27 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge return false; } + function eachInstance(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) { + let changed = false; + if (!StructureElement.Loci.is(loci) && !Bond.isLoci(loci)) return false; + if (!Structure.areEquivalent(loci.structure, structure)) return false; + if (apply(Interval.ofSingleton(0))) changed = true; + return changed; + } + function lociApply(loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) { if (lociIsSuperset(loci)) { - return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount)); + if (currentProps.instanceGranularity) { + return apply(Interval.ofBounds(0, locationIt.instanceCount)); + } else { + return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount)); + } } else { - return eachLocation(loci, currentStructure, apply, isMarking); + if (currentProps.instanceGranularity) { + return eachInstance(loci, currentStructure, apply); + } else { + return eachLocation(loci, currentStructure, apply, isMarking); + } } } diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index 2325d08b91d80ce263548634d5a19b31b1fcaafd..8c0e3a1d8b7e25488410100f02594b508ed4367a 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -5,7 +5,7 @@ */ import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { Structure, Unit, StructureElement } from '../../mol-model/structure'; +import { Structure, Unit, StructureElement, Bond } from '../../mol-model/structure'; import { RepresentationProps } from '../representation'; import { Visual, VisualContext } from '../visual'; import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry'; @@ -126,6 +126,10 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom updateState.createGeometry = true; } + if (newProps.instanceGranularity !== currentProps.instanceGranularity) { + updateState.updateTransform = true; + } + if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) { // console.log('new unitKinds'); updateState.createGeometry = true; @@ -194,7 +198,11 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom // console.log('update transform'); locationIt = createLocationIterator(newStructureGroup); const { instanceCount, groupCount } = locationIt; - createMarkers(instanceCount * groupCount, renderObject.values); + if (newProps.instanceGranularity) { + createMarkers(instanceCount, 'instance', renderObject.values); + } else { + createMarkers(instanceCount * groupCount, 'groupInstance', renderObject.values); + } } if (updateState.updateMatrix) { @@ -259,11 +267,44 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom return false; } + function eachInstance(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) { + let changed = false; + if (Bond.isLoci(loci)) { + const { structure, group } = structureGroup; + if (!Structure.areEquivalent(loci.structure, structure)) return false; + for (const b of loci.bonds) { + if (b.aUnit !== b.bUnit) continue; + const unitIdx = group.unitIndexMap.get(b.aUnit.id); + if (unitIdx !== undefined) { + if (apply(Interval.ofSingleton(unitIdx))) changed = true; + } + } + } else if (StructureElement.Loci.is(loci)) { + const { structure, group } = structureGroup; + if (!Structure.areEquivalent(loci.structure, structure)) return false; + for (const e of loci.elements) { + const unitIdx = group.unitIndexMap.get(e.unit.id); + if (unitIdx !== undefined) { + if (apply(Interval.ofSingleton(unitIdx))) changed = true; + } + } + } + return changed; + } + function lociApply(loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) { if (lociIsSuperset(loci)) { - return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount)); + if (currentProps.instanceGranularity) { + return apply(Interval.ofBounds(0, locationIt.instanceCount)); + } else { + return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount)); + } } else { - return eachLocation(loci, currentStructureGroup, apply, isMarking); + if (currentProps.instanceGranularity) { + return eachInstance(loci, currentStructureGroup, apply); + } else { + return eachLocation(loci, currentStructureGroup, apply, isMarking); + } } } diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts index 90e272b4cb925e35626927f7e9e40b40bc475eb3..924e0fb81fa141c54b0b3e001f4b4fafeb02a3c2 100644 --- a/src/mol-repr/visual.ts +++ b/src/mol-repr/visual.ts @@ -82,8 +82,10 @@ namespace Visual { export function mark(renderObject: GraphicsRenderObject | undefined, loci: Loci, action: MarkerAction, lociApply: LociApply, previous?: PreviousMark) { if (!renderObject || isEmptyLoci(loci)) return false; - const { tMarker, uMarker, markerAverage, markerStatus, uGroupCount, instanceCount } = renderObject.values; - const count = uGroupCount.ref.value * instanceCount.ref.value; + const { tMarker, uMarker, markerAverage, markerStatus, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values; + const count = instanceGranularity.ref.value + ? instanceCount.ref.value + : uGroupCount.ref.value * instanceCount.ref.value; const { array } = tMarker.ref.value; const currentStatus = markerStatus.ref.value as MarkerInfo['status']; @@ -158,11 +160,14 @@ namespace Visual { export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) { if (!renderObject) return; - const { tOverpaint, dOverpaintType, dOverpaint, uGroupCount, instanceCount } = renderObject.values; - const count = uGroupCount.ref.value * instanceCount.ref.value; + const { tOverpaint, dOverpaintType, dOverpaint, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values; + const count = instanceGranularity.ref.value + ? instanceCount.ref.value + : uGroupCount.ref.value * instanceCount.ref.value; - // ensure texture has right size - createOverpaint(overpaint.layers.length ? count : 0, renderObject.values); + // ensure texture has right size and type + const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance'; + createOverpaint(overpaint.layers.length ? count : 0, type, renderObject.values); const { array } = tOverpaint.ref.value; // clear all if requested @@ -180,10 +185,11 @@ namespace Visual { lociApply(loci, apply, false); } ValueCell.update(tOverpaint, tOverpaint.ref.value); - ValueCell.updateIfChanged(dOverpaintType, 'groupInstance'); + ValueCell.updateIfChanged(dOverpaintType, type); ValueCell.updateIfChanged(dOverpaint, overpaint.layers.length > 0); if (overpaint.layers.length === 0) return; + if (type === 'instance') return; if (smoothing && hasColorSmoothingProp(smoothing.props)) { const { geometry, props, webgl } = smoothing; @@ -208,11 +214,14 @@ namespace Visual { export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) { if (!renderObject) return; - const { tTransparency, dTransparencyType, transparencyAverage, dTransparency, uGroupCount, instanceCount } = renderObject.values; - const count = uGroupCount.ref.value * instanceCount.ref.value; + const { tTransparency, dTransparencyType, transparencyAverage, dTransparency, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values; + const count = instanceGranularity.ref.value + ? instanceCount.ref.value + : uGroupCount.ref.value * instanceCount.ref.value; - // ensure texture has right size and variant - createTransparency(transparency.layers.length ? count : 0, renderObject.values); + // ensure texture has right size and type + const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance'; + createTransparency(transparency.layers.length ? count : 0, type, renderObject.values); const { array } = tTransparency.ref.value; // clear if requested @@ -229,10 +238,11 @@ namespace Visual { } ValueCell.update(tTransparency, tTransparency.ref.value); ValueCell.updateIfChanged(transparencyAverage, getTransparencyAverage(array, count)); - ValueCell.updateIfChanged(dTransparencyType, 'groupInstance'); + ValueCell.updateIfChanged(dTransparencyType, type); ValueCell.updateIfChanged(dTransparency, transparency.layers.length > 0); if (transparency.layers.length === 0) return; + if (type === 'instance') return; if (smoothing && hasColorSmoothingProp(smoothing.props)) { const { geometry, props, webgl } = smoothing; @@ -257,11 +267,14 @@ namespace Visual { export function setSubstance(renderObject: GraphicsRenderObject | undefined, substance: Substance, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) { if (!renderObject) return; - const { tSubstance, dSubstanceType, dSubstance, uGroupCount, instanceCount } = renderObject.values; - const count = uGroupCount.ref.value * instanceCount.ref.value; + const { tSubstance, dSubstanceType, dSubstance, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values; + const count = instanceGranularity.ref.value + ? instanceCount.ref.value + : uGroupCount.ref.value * instanceCount.ref.value; - // ensure texture has right size - createSubstance(substance.layers.length ? count : 0, renderObject.values); + // ensure texture has right size and type + const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance'; + createSubstance(substance.layers.length ? count : 0, type, renderObject.values); const { array } = tSubstance.ref.value; // clear all if requested @@ -279,10 +292,11 @@ namespace Visual { lociApply(loci, apply, false); } ValueCell.update(tSubstance, tSubstance.ref.value); - ValueCell.updateIfChanged(dSubstanceType, 'groupInstance'); + ValueCell.updateIfChanged(dSubstanceType, type); ValueCell.updateIfChanged(dSubstance, substance.layers.length > 0); if (substance.layers.length === 0) return; + if (type === 'instance') return; if (smoothing && hasColorSmoothingProp(smoothing.props)) { const { geometry, props, webgl } = smoothing; @@ -307,12 +321,15 @@ namespace Visual { export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) { if (!renderObject) return; - const { tClipping, dClipping, uGroupCount, instanceCount } = renderObject.values; - const count = uGroupCount.ref.value * instanceCount.ref.value; + const { tClipping, dClippingType, dClipping, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values; + const count = instanceGranularity.ref.value + ? instanceCount.ref.value + : uGroupCount.ref.value * instanceCount.ref.value; const { layers } = clipping; - // ensure texture has right size - createClipping(layers.length ? count : 0, renderObject.values); + // ensure texture has right size and type + const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance'; + createClipping(layers.length ? count : 0, type, renderObject.values); const { array } = tClipping.ref.value; // clear if requested @@ -328,6 +345,7 @@ namespace Visual { lociApply(loci, apply, false); } ValueCell.update(tClipping, tClipping.ref.value); + ValueCell.updateIfChanged(dClippingType, type); ValueCell.updateIfChanged(dClipping, clipping.layers.length > 0); } diff --git a/src/mol-repr/volume/representation.ts b/src/mol-repr/volume/representation.ts index bb90f67659a04e2192ad3e5367eac3da97204eb0..fae57da2554529ff0eee4d13a229d8cc6c956271 100644 --- a/src/mol-repr/volume/representation.ts +++ b/src/mol-repr/volume/representation.ts @@ -33,6 +33,7 @@ import { Clipping } from '../../mol-theme/clipping'; import { WebGLContext } from '../../mol-gl/webgl/context'; import { isPromiseLike } from '../../mol-util/type-helpers'; import { Substance } from '../../mol-theme/substance'; +import { createMarkers } from '../../mol-geo/geometry/marker-data'; export interface VolumeVisual<P extends VolumeParams> extends Visual<Volume, P> { } @@ -108,6 +109,10 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet if (updateState.createGeometry) { updateState.updateColor = true; } + + if (newProps.instanceGranularity !== currentProps.instanceGranularity) { + updateState.updateTransform = true; + } } function update(newGeometry?: G) { @@ -124,7 +129,18 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet throw new Error('expected renderObject to be available'); } - locationIt.reset(); + if (updateState.updateTransform) { + // console.log('update transform'); + locationIt = createLocationIterator(newVolume); + const { instanceCount, groupCount } = locationIt; + if (newProps.instanceGranularity) { + createMarkers(instanceCount, 'instance', renderObject.values); + } else { + createMarkers(instanceCount * groupCount, 'groupInstance', renderObject.values); + } + } else { + locationIt.reset(); + } if (updateState.createGeometry) { if (newGeometry) { @@ -165,11 +181,28 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet } } + function eachInstance(loci: Loci, volume: Volume, apply: (interval: Interval) => boolean) { + let changed = false; + if (!Volume.Cell.isLoci(loci)) return false; + if (Volume.Cell.isLociEmpty(loci)) return false; + if (!Volume.areEquivalent(loci.volume, volume)) return false; + if (apply(Interval.ofSingleton(0))) changed = true; + return changed; + } + function lociApply(loci: Loci, apply: (interval: Interval) => boolean) { if (isEveryLoci(loci)) { - return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount)); + if (currentProps.instanceGranularity) { + return apply(Interval.ofBounds(0, locationIt.instanceCount)); + } else { + return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount)); + } } else { - return eachLocation(loci, currentVolume, currentProps, apply); + if (currentProps.instanceGranularity) { + return eachInstance(loci, currentVolume, apply); + } else { + return eachLocation(loci, currentVolume, currentProps, apply); + } } }