diff --git a/src/mol-model/loci.ts b/src/mol-model/loci.ts index 3064ccfb8ca1afa26e0e21686f8b230230413b31..d3158ac857d2c6ab1ff831c084cfb08df8819e21 100644 --- a/src/mol-model/loci.ts +++ b/src/mol-model/loci.ts @@ -76,13 +76,13 @@ namespace Loci { return false } - export function isEmpty(loci: Loci) { + export function isEmpty(loci: Loci): boolean { if (isEveryLoci(loci)) return false if (isEmptyLoci(loci)) return true if (isDataLoci(loci)) return isDataLociEmpty(loci) if (Structure.isLoci(loci)) return Structure.isLociEmpty(loci) - if (StructureElement.Loci.is(loci)) StructureElement.Loci.isEmpty(loci) - if (Link.isLoci(loci)) Link.isLociEmpty(loci) + if (StructureElement.Loci.is(loci)) return StructureElement.Loci.isEmpty(loci) + if (Link.isLoci(loci)) return Link.isLociEmpty(loci) if (Shape.isLoci(loci)) return Shape.isLociEmpty(loci) if (ShapeGroup.isLoci(loci)) return ShapeGroup.isLociEmpty(loci) return false diff --git a/src/mol-plugin/util/structure-overpaint-helper.ts b/src/mol-plugin/util/structure-overpaint-helper.ts index 7424be71533be7cf2d59720f47b87c81c374d113..05d7ea690925ad9ce6b893b9be46daafeb38c0a7 100644 --- a/src/mol-plugin/util/structure-overpaint-helper.ts +++ b/src/mol-plugin/util/structure-overpaint-helper.ts @@ -10,6 +10,7 @@ import { StateSelection, StateObjectCell, StateTransform, StateBuilder } from '. import { Structure, StructureElement } from '../../mol-model/structure'; import { PluginContext } from '../context'; import { Color } from '../../mol-util/color'; +import { Overpaint } from '../../mol-theme/overpaint'; type OverpaintEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, overpaint?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle>>) => void const OverpaintManagerTag = 'overpaint-controls' @@ -29,12 +30,9 @@ export class StructureOverpaintHelper { } async set(color: Color | -1, lociGetter: (structure: Structure) => StructureElement.Loci, types?: string[]) { - await this.eachRepr((update, repr, overpaint) => { + await this.eachRepr((update, repr, overpaintCell) => { if (types && !types.includes(repr.params!.values.type.name)) return - // TODO merge overpaint layers, delete shadowed ones - // TODO filter overpaint layers for given structure - const structure = repr.obj!.data.source.data // always use the root structure to get the loci so the overpaint // stays applicable as long as the root structure does not change @@ -47,11 +45,14 @@ export class StructureOverpaintHelper { clear: color === -1 } - if (overpaint) { - update.to(overpaint).update({ layers: [ ...overpaint.params!.values.layers, layer ], alpha: 1 }) + if (overpaintCell) { + const bundleLayers = [ ...overpaintCell.params!.values.layers, layer ] + const filtered = getFilteredBundle(bundleLayers, structure) + update.to(overpaintCell).update(Overpaint.toBundle(filtered, 1)) } else { + const filtered = getFilteredBundle([ layer ], structure) update.to(repr.transform.ref) - .apply(StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle, { layers: [ layer ], alpha: 1 }, { tags: OverpaintManagerTag }); + .apply(StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle, Overpaint.toBundle(filtered, 1), { tags: OverpaintManagerTag }); } }) } @@ -59,4 +60,11 @@ export class StructureOverpaintHelper { constructor(private plugin: PluginContext) { } +} + +/** filter overpaint layers for given structure */ +function getFilteredBundle(layers: Overpaint.BundleLayer[], structure: Structure) { + const overpaint = Overpaint.ofBundle(layers, 1, structure.root) + const merged = Overpaint.merge(overpaint) + return Overpaint.filter(merged, structure) } \ No newline at end of file diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index c7a98b4336f9bfebe37c4fa4995eabb55a63f52f..9e3e2577e248fc9e6e78b749026113ee2c5fb2a5 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -184,8 +184,10 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R if (pickable !== undefined) visuals.forEach(({ visual }) => visual.setPickable(pickable)) if (overpaint !== undefined) { // Remap loci from equivalent structure to the current `_structure` - const remappedOverpaint = Overpaint.remap(overpaint, _structure) - visuals.forEach(({ visual }) => visual.setOverpaint(remappedOverpaint)) + if (_structure) { + const remappedOverpaint = Overpaint.remap(overpaint, _structure) + visuals.forEach(({ visual }) => visual.setOverpaint(remappedOverpaint)) + } } if (transparency !== undefined) visuals.forEach(({ visual }) => visual.setTransparency(transparency)) if (transform !== undefined) visuals.forEach(({ visual }) => visual.setTransform(transform)) diff --git a/src/mol-theme/overpaint.ts b/src/mol-theme/overpaint.ts index b7de9494417c0c5c7044a681a846331f250b9683..1263e585d327f68f68840e4d3a86f8b56498a90c 100644 --- a/src/mol-theme/overpaint.ts +++ b/src/mol-theme/overpaint.ts @@ -11,10 +11,14 @@ import { Script } from '../mol-script/script'; export { Overpaint } -type Overpaint = { layers: ReadonlyArray<Overpaint.Layer>, readonly alpha: number } +type Overpaint = { readonly layers: ReadonlyArray<Overpaint.Layer>, readonly alpha: number } + +function Overpaint(layers: ReadonlyArray<Overpaint.Layer>, alpha: number): Overpaint { + return { layers, alpha } +} namespace Overpaint { - export type Layer = { readonly loci: Loci, readonly color: Color, readonly clear: boolean } + export type Layer = { readonly loci: StructureElement.Loci, readonly color: Color, readonly clear: boolean } export const Empty: Overpaint = { layers: [], alpha: 1 } export function areEqual(oA: Overpaint, oB: Overpaint) { @@ -29,25 +33,80 @@ namespace Overpaint { return true } + export function isEmpty(overpaint: Overpaint) { + return overpaint.layers.length === 0 + } + export function remap(overpaint: Overpaint, structure: Structure) { const layers: Overpaint.Layer[] = [] for (const layer of overpaint.layers) { - const { loci, color, clear } = layer - layers.push({ loci: Loci.remap(loci, structure), color, clear }) + let { loci, color, clear } = layer + loci = StructureElement.Loci.remap(loci, structure) + if (!StructureElement.Loci.isEmpty(loci)) { + layers.push({ loci, color, clear }) + } + } + return { layers, alpha: overpaint.alpha } + } + + export function merge(overpaint: Overpaint): Overpaint { + if (isEmpty(overpaint)) return overpaint + const { structure } = overpaint.layers[0].loci + const map = new Map<Color | -1, StructureElement.Loci>() + let shadowed = StructureElement.Loci.none(structure) + for (let i = 0, il = overpaint.layers.length; i < il; ++i) { + let { loci, color, clear } = overpaint.layers[il - i - 1] // process from end + loci = StructureElement.Loci.subtract(loci, shadowed) + shadowed = StructureElement.Loci.union(loci, shadowed) + if (!StructureElement.Loci.isEmpty(loci)) { + const colorOrClear = clear ? -1 : color + if (map.has(colorOrClear)) { + loci = StructureElement.Loci.union(loci, map.get(colorOrClear)!) + } + map.set(colorOrClear, loci) + } } + const layers: Overpaint.Layer[] = [] + map.forEach((loci, colorOrClear) => { + const clear = colorOrClear === -1 + const color = colorOrClear === -1 ? Color(0) : colorOrClear + layers.push({ loci, color, clear }) + }) return { layers, alpha: overpaint.alpha } } - export function ofScript(scriptLayers: { script: Script, color: Color, clear: boolean }[], alpha: number, structure: Structure): Overpaint { + export function filter(overpaint: Overpaint, filter: Structure): Overpaint { + if (isEmpty(overpaint)) return overpaint + const { structure } = overpaint.layers[0].loci + const layers: Overpaint.Layer[] = [] + for (const layer of overpaint.layers) { + let { loci, color, clear } = layer + // filter by first map to the `filter` structure and + // then map back to the original structure of the overpaint loci + const filtered = StructureElement.Loci.remap(loci, filter) + loci = StructureElement.Loci.remap(filtered, structure) + if (!StructureElement.Loci.isEmpty(loci)) { + layers.push({ loci, color, clear }) + } + } + return { layers, alpha: overpaint.alpha } + } + + export type ScriptLayer = { script: Script, color: Color, clear: boolean } + export function ofScript(scriptLayers: ScriptLayer[], alpha: number, structure: Structure): Overpaint { const layers: Overpaint.Layer[] = [] for (let i = 0, il = scriptLayers.length; i < il; ++i) { const { script, color, clear } = scriptLayers[i] - layers.push({ loci: Script.toLoci(script, structure), color, clear }) + const loci = Script.toLoci(script, structure) + if (!StructureElement.Loci.isEmpty(loci)) { + layers.push({ loci, color, clear }) + } } return { layers, alpha } } - export function ofBundle(bundleLayers: { bundle: StructureElement.Bundle, color: Color, clear: boolean }[], alpha: number, structure: Structure): Overpaint { + export type BundleLayer = { bundle: StructureElement.Bundle, color: Color, clear: boolean } + export function ofBundle(bundleLayers: BundleLayer[], alpha: number, structure: Structure): Overpaint { const layers: Overpaint.Layer[] = [] for (let i = 0, il = bundleLayers.length; i < il; ++i) { const { bundle, color, clear } = bundleLayers[i] @@ -56,4 +115,14 @@ namespace Overpaint { } return { layers, alpha } } + + export function toBundle(overpaint: Overpaint, alpha: number) { + const layers: BundleLayer[] = [] + for (let i = 0, il = overpaint.layers.length; i < il; ++i) { + let { loci, color, clear } = overpaint.layers[i] + const bundle = StructureElement.Bundle.fromLoci(loci) + layers.push({ bundle, color, clear }) + } + return { layers, alpha } + } } \ No newline at end of file