diff --git a/CHANGELOG.md b/CHANGELOG.md index 216d17fc344a5773b94dd7fccab4f3629a6754f4..73a77d9e04c2e9f675319c2256d72eccc90be4af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,18 @@ Note that since we don't clearly distinguish between a public and private interf - Add ``Mesh`` processing helper ``.smoothEdges`` - Smooth border of molecular-surface with ``includeParent`` enabled - Hide ``includeParent`` option from gaussian-surface visuals (not particularly useful) -- Improved ``StructureElement.Loci.size`` performance (for marking large cellpack models) - Fix new ``TransformData`` issues (camera/bounding helper not showing up) -- Improve marking performance (avoid superfluous calls to ``StructureElement.Loci.isWholeStructure``) +- Improve marking performance + - Avoid superfluous calls to ``StructureElement.Loci.isWholeStructure`` + - Check if loci is superset of visual + - Check if loci overlaps with unit visual + - Ensure ``Interval`` is used for ranges instead of ``SortedArray`` + - Inline ``StructureElement.Loci.size`` code + - Add uniform marker type + - Special case for reversing previous mark +- Add optional marking pass + - Outlines visible and hidden parts of highlighted/selected groups + - Add highlightStrength/selectStrength renderer params ## [v2.2.2] - 2021-08-11 diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 94e9c45f1acc7303d9ff950c01322e23d746588f..bae279c26dd5d5b46d802d313a85af9cbe97b7c2 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -38,6 +38,7 @@ import { StereoCamera, StereoCameraParams } from './camera/stereo'; import { Helper } from './helper/helper'; import { Passes } from './passes/passes'; import { shallowEqual } from '../mol-util'; +import { MarkingParams } from './passes/marking'; export const Canvas3DParams = { camera: PD.Group({ @@ -80,6 +81,7 @@ export const Canvas3DParams = { multiSample: PD.Group(MultiSampleParams), postprocessing: PD.Group(PostprocessingParams), + marking: PD.Group(MarkingParams), renderer: PD.Group(RendererParams), trackball: PD.Group(TrackballControlsParams), debug: PD.Group(DebugHelperParams), @@ -390,7 +392,7 @@ namespace Canvas3D { if (MultiSamplePass.isEnabled(p.multiSample)) { multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p); } else { - passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing); + passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing, p.marking); } pickHelper.dirty = true; didRender = true; @@ -636,6 +638,7 @@ namespace Canvas3D { viewport: p.viewport, postprocessing: { ...p.postprocessing }, + marking: { ...p.marking }, multiSample: { ...p.multiSample }, renderer: { ...renderer.props }, trackball: { ...controls.props }, @@ -771,6 +774,7 @@ namespace Canvas3D { } if (props.postprocessing) Object.assign(p.postprocessing, props.postprocessing); + if (props.marking) Object.assign(p.marking, props.marking); if (props.multiSample) Object.assign(p.multiSample, props.multiSample); if (props.renderer) renderer.setProps(props.renderer); if (props.trackball) controls.setProps(props.trackball); @@ -835,9 +839,6 @@ namespace Canvas3D { height = Math.round(p.viewport.params.height * gl.drawingBufferHeight); y = Math.round(gl.drawingBufferHeight - height - p.viewport.params.y * gl.drawingBufferHeight); width = Math.round(p.viewport.params.width * gl.drawingBufferWidth); - // if (x + width >= gl.drawingBufferWidth) width = gl.drawingBufferWidth - x; - // if (y + height >= gl.drawingBufferHeight) height = gl.drawingBufferHeight - y - 1; - // console.log({ x, y, width, height }); } if (oldX !== x || oldY !== y || oldWidth !== width || oldHeight !== height) { diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index 8fbebe98571b93cd700cda71eaa11440b22b96b2..90373c0dfb3e0589544eec4b8d777f632a895f98 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -26,6 +26,7 @@ import { copy_frag } from '../../mol-gl/shader/copy.frag'; import { StereoCamera } from '../camera/stereo'; import { WboitPass } from './wboit'; import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing'; +import { MarkingPass, MarkingProps } from './marking'; const DepthMergeSchema = { ...QuadSchema, @@ -92,6 +93,7 @@ export class DrawPass { private copyFboPostprocessing: CopyRenderable private wboit: WboitPass | undefined + private readonly marking: MarkingPass readonly postprocessing: PostprocessingPass private readonly antialiasing: AntialiasingPass @@ -122,6 +124,7 @@ export class DrawPass { this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth); this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined; + this.marking = new MarkingPass(webgl, width, height); this.postprocessing = new PostprocessingPass(webgl, this); this.antialiasing = new AntialiasingPass(webgl, this); @@ -162,6 +165,7 @@ export class DrawPass { this.wboit.setSize(width, height); } + this.marking.setSize(width, height); this.postprocessing.setSize(width, height); this.antialiasing.setSize(width, height); } @@ -281,10 +285,11 @@ export class DrawPass { renderer.renderBlendedTransparent(scene.primitives, camera, null); } - private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) { + private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) { const volumeRendering = scene.volumes.renderables.length > 0; const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps); const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps); + const markingEnabled = MarkingPass.isEnabled(markingProps); const { x, y, width, height } = camera.viewport; renderer.setViewport(x, y, width, height); @@ -309,6 +314,22 @@ export class DrawPass { this.drawTarget.bind(); } + if (markingEnabled) { + const markingDepthTest = markingProps.ghostEdgeStrength < 1; + if (markingDepthTest) { + this.marking.depthTarget.bind(); + renderer.clear(false); + renderer.renderMarkingDepth(scene.primitives, camera, null); + } + + this.marking.maskTarget.bind(); + renderer.clear(false); + renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null); + + this.marking.update(markingProps); + this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget); + } + if (helper.debug.isEnabled) { helper.debug.syncVisibility(); renderer.renderBlended(helper.debug.scene, camera, null); @@ -338,15 +359,15 @@ export class DrawPass { this.webgl.gl.flush(); } - render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) { + render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) { renderer.setTransparentBackground(transparentBackground); renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight()); if (StereoCamera.is(camera)) { - this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps); - this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps); + this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps); + this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps); } else { - this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps); + this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps); } } diff --git a/src/mol-canvas3d/passes/image.ts b/src/mol-canvas3d/passes/image.ts index f27ff51223712cf37c68c51a79101adff640685e..bf56b9300e317f26da3ccc1bdd96c8c3e3c51c81 100644 --- a/src/mol-canvas3d/passes/image.ts +++ b/src/mol-canvas3d/passes/image.ts @@ -17,11 +17,13 @@ import { Viewport } from '../camera/util'; import { PixelData } from '../../mol-util/image'; import { Helper } from '../helper/helper'; import { CameraHelper, CameraHelperParams } from '../helper/camera-helper'; +import { MarkingParams } from './marking'; export const ImageParams = { transparentBackground: PD.Boolean(false), multiSample: PD.Group(MultiSampleParams), postprocessing: PD.Group(PostprocessingParams), + marking: PD.Group(MarkingParams), cameraHelper: PD.Group(CameraHelperParams), }; @@ -85,7 +87,7 @@ export class ImagePass { this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props); this._colorTarget = this.multiSamplePass.colorTarget; } else { - this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing); + this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing, this.props.marking); this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing); } } diff --git a/src/mol-canvas3d/passes/marking.ts b/src/mol-canvas3d/passes/marking.ts new file mode 100644 index 0000000000000000000000000000000000000000..df19ea731db269c87f2be5873c97891afdc4d861 --- /dev/null +++ b/src/mol-canvas3d/passes/marking.ts @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { QuadSchema, QuadValues } from '../../mol-gl/compute/util'; +import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable'; +import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema'; +import { ShaderCode } from '../../mol-gl/shader-code'; +import { WebGLContext } from '../../mol-gl/webgl/context'; +import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; +import { Texture } from '../../mol-gl/webgl/texture'; +import { Vec2, Vec3 } from '../../mol-math/linear-algebra'; +import { ValueCell } from '../../mol-util'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { quad_vert } from '../../mol-gl/shader/quad.vert'; +import { overlay_frag } from '../../mol-gl/shader/marking/overlay.frag'; +import { Viewport } from '../camera/util'; +import { RenderTarget } from '../../mol-gl/webgl/render-target'; +import { Color } from '../../mol-util/color'; +import { edge_frag } from '../../mol-gl/shader/marking/edge.frag'; + +export const MarkingParams = { + enabled: PD.Boolean(false), + highlightEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(1.0, 0.4, 0.6), 1.0)), + selectEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(0.2, 1.0, 0.1), 1.0)), + edgeScale: PD.Numeric(1, { min: 1, max: 3, step: 1 }, { description: 'Thickness of the edge.' }), + ghostEdgeStrength: PD.Numeric(0.3, { min: 0, max: 1, step: 0.1 }, { description: 'Opacity of the hidden edges that are covered by other geometry. When set to 1, one less geometry render pass is done.' }), + innerEdgeFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }, { description: 'Factor to multiply the inner edge color with - for added contrast.' }), +}; +export type MarkingProps = PD.Values<typeof MarkingParams> + +export class MarkingPass { + static isEnabled(props: MarkingProps) { + return props.enabled; + } + + readonly depthTarget: RenderTarget + readonly maskTarget: RenderTarget + private readonly edgesTarget: RenderTarget + + private readonly edge: EdgeRenderable + private readonly overlay: OverlayRenderable + + constructor(private webgl: WebGLContext, width: number, height: number) { + this.depthTarget = webgl.createRenderTarget(width, height); + this.maskTarget = webgl.createRenderTarget(width, height); + this.edgesTarget = webgl.createRenderTarget(width, height); + + this.edge = getEdgeRenderable(webgl, this.maskTarget.texture); + this.overlay = getOverlayRenderable(webgl, this.edgesTarget.texture); + } + + private setEdgeState(viewport: Viewport) { + const { gl, state } = this.webgl; + + state.enable(gl.SCISSOR_TEST); + state.enable(gl.BLEND); + state.blendFunc(gl.ONE, gl.ONE); + state.blendEquation(gl.FUNC_ADD); + state.disable(gl.DEPTH_TEST); + state.depthMask(false); + + const { x, y, width, height } = viewport; + gl.viewport(x, y, width, height); + gl.scissor(x, y, width, height); + + state.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + } + + private setOverlayState(viewport: Viewport) { + const { gl, state } = this.webgl; + + state.enable(gl.SCISSOR_TEST); + state.enable(gl.BLEND); + state.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + state.blendEquation(gl.FUNC_ADD); + state.disable(gl.DEPTH_TEST); + state.depthMask(false); + + const { x, y, width, height } = viewport; + gl.viewport(x, y, width, height); + gl.scissor(x, y, width, height); + } + + setSize(width: number, height: number) { + const w = this.depthTarget.getWidth(); + const h = this.depthTarget.getHeight(); + + if (width !== w || height !== h) { + this.depthTarget.setSize(width, height); + this.maskTarget.setSize(width, height); + this.edgesTarget.setSize(width, height); + + ValueCell.update(this.edge.values.uTexSizeInv, Vec2.set(this.edge.values.uTexSizeInv.ref.value, 1 / width, 1 / height)); + ValueCell.update(this.overlay.values.uTexSizeInv, Vec2.set(this.overlay.values.uTexSizeInv.ref.value, 1 / width, 1 / height)); + } + } + + update(props: MarkingProps) { + const { highlightEdgeColor, selectEdgeColor, edgeScale, innerEdgeFactor, ghostEdgeStrength } = props; + + const { values: edgeValues } = this.edge; + const _edgeScale = Math.round(edgeScale * this.webgl.pixelRatio); + if (edgeValues.dEdgeScale.ref.value !== _edgeScale) { + ValueCell.update(edgeValues.dEdgeScale, _edgeScale); + this.edge.update(); + } + + const { values: overlayValues } = this.overlay; + ValueCell.update(overlayValues.uHighlightEdgeColor, Color.toVec3Normalized(overlayValues.uHighlightEdgeColor.ref.value, highlightEdgeColor)); + ValueCell.update(overlayValues.uSelectEdgeColor, Color.toVec3Normalized(overlayValues.uSelectEdgeColor.ref.value, selectEdgeColor)); + ValueCell.update(overlayValues.uInnerEdgeFactor, innerEdgeFactor); + ValueCell.update(overlayValues.uGhostEdgeStrength, ghostEdgeStrength); + } + + render(viewport: Viewport, target: RenderTarget | undefined) { + this.edgesTarget.bind(); + this.setEdgeState(viewport); + this.edge.render(); + + if (target) { + target.bind(); + } else { + this.webgl.unbindFramebuffer(); + } + this.setOverlayState(viewport); + this.overlay.render(); + } +} + +// + +const EdgeSchema = { + ...QuadSchema, + tMaskTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), + uTexSizeInv: UniformSpec('v2'), + dEdgeScale: DefineSpec('number'), +}; +const EdgeShaderCode = ShaderCode('edge', quad_vert, edge_frag); +type EdgeRenderable = ComputeRenderable<Values<typeof EdgeSchema>> + +function getEdgeRenderable(ctx: WebGLContext, maskTexture: Texture): EdgeRenderable { + const width = maskTexture.getWidth(); + const height = maskTexture.getHeight(); + + const values: Values<typeof EdgeSchema> = { + ...QuadValues, + tMaskTexture: ValueCell.create(maskTexture), + uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)), + dEdgeScale: ValueCell.create(1), + }; + + const schema = { ...EdgeSchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', EdgeShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} + +// + +const OverlaySchema = { + ...QuadSchema, + tEdgeTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), + uTexSizeInv: UniformSpec('v2'), + uHighlightEdgeColor: UniformSpec('v3'), + uSelectEdgeColor: UniformSpec('v3'), + uGhostEdgeStrength: UniformSpec('f'), + uInnerEdgeFactor: UniformSpec('f'), +}; +const OverlayShaderCode = ShaderCode('overlay', quad_vert, overlay_frag); +type OverlayRenderable = ComputeRenderable<Values<typeof OverlaySchema>> + +function getOverlayRenderable(ctx: WebGLContext, edgeTexture: Texture): OverlayRenderable { + const width = edgeTexture.getWidth(); + const height = edgeTexture.getHeight(); + + const values: Values<typeof OverlaySchema> = { + ...QuadValues, + tEdgeTexture: ValueCell.create(edgeTexture), + uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)), + uHighlightEdgeColor: ValueCell.create(Vec3()), + uSelectEdgeColor: ValueCell.create(Vec3()), + uGhostEdgeStrength: ValueCell.create(0), + uInnerEdgeFactor: ValueCell.create(0), + }; + + const schema = { ...OverlaySchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', OverlayShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} \ No newline at end of file diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts index a3b9c62497059e44bb0c3e9b65a37c98cf2c630e..a4b56b20432db182d674eb834c9ada999b407d46 100644 --- a/src/mol-canvas3d/passes/multi-sample.ts +++ b/src/mol-canvas3d/passes/multi-sample.ts @@ -22,9 +22,9 @@ import { Renderer } from '../../mol-gl/renderer'; import { Scene } from '../../mol-gl/scene'; import { Helper } from '../helper/helper'; import { StereoCamera } from '../camera/stereo'; - import { quad_vert } from '../../mol-gl/shader/quad.vert'; import { compose_frag } from '../../mol-gl/shader/compose.frag'; +import { MarkingProps } from './marking'; const ComposeSchema = { ...QuadSchema, @@ -55,7 +55,11 @@ export const MultiSampleParams = { }; export type MultiSampleProps = PD.Values<typeof MultiSampleParams> -type Props = { multiSample: MultiSampleProps, postprocessing: PostprocessingProps } +type Props = { + multiSample: MultiSampleProps + postprocessing: PostprocessingProps + marking: MarkingProps +} export class MultiSamplePass { static isEnabled(props: MultiSampleProps) { @@ -144,7 +148,7 @@ export class MultiSamplePass { ValueCell.update(compose.values.uWeight, sampleWeight); // render scene - drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing); + drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking); // compose rendered scene with compose target composeTarget.bind(); @@ -194,7 +198,7 @@ export class MultiSamplePass { const sampleWeight = 1.0 / offsetList.length; if (sampleIndex === -1) { - drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing); + drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking); ValueCell.update(compose.values.uWeight, 1.0); ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture); compose.update(); @@ -222,7 +226,7 @@ export class MultiSamplePass { camera.update(); // render scene - drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing); + drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking); // compose rendered scene with compose target composeTarget.bind(); diff --git a/src/mol-data/int/impl/ordered-set.ts b/src/mol-data/int/impl/ordered-set.ts index 762ae81477a86ba2243981fae6629cdc693cea50..52d6f8362de4aa9e1684c5a1467300215a4333a5 100644 --- a/src/mol-data/int/impl/ordered-set.ts +++ b/src/mol-data/int/impl/ordered-set.ts @@ -19,7 +19,7 @@ export const ofBounds = I.ofBounds; export function ofSortedArray(xs: Nums): OrderedSetImpl { if (!xs.length) return Empty; // check if the array is just a range - if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return I.ofRange(xs[0], xs[xs.length - 1]); + if (S.isRange(xs)) return I.ofRange(xs[0], xs[xs.length - 1]); return xs as any; } diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts index fb5dfc950d353731d93dabb1f877afa8e1aeb019..1e857f27bddfd75d354fe5c7874901e1c889fd8b 100644 --- a/src/mol-data/int/impl/sorted-array.ts +++ b/src/mol-data/int/impl/sorted-array.ts @@ -22,6 +22,7 @@ export function ofRange(min: number, max: number) { return ret; } export function is(xs: any): xs is Nums { return xs && (Array.isArray(xs) || !!xs.buffer); } +export function isRange(xs: Nums) { return xs[xs.length - 1] - xs[0] + 1 === xs.length; } export function start(xs: Nums) { return xs[0]; } export function end(xs: Nums) { return xs[xs.length - 1] + 1; } @@ -59,9 +60,11 @@ export function getAt(xs: Nums, i: number) { return xs[i]; } export function areEqual(a: Nums, b: Nums) { if (a === b) return true; - const aSize = a.length; + let aSize = a.length; if (aSize !== b.length || a[0] !== b[0] || a[aSize - 1] !== b[aSize - 1]) return false; - for (let i = 0; i < aSize; i++) { + if (isRange(a)) return true; + aSize--; + for (let i = 1; i < aSize; i++) { if (a[i] !== b[i]) return false; } return true; @@ -340,7 +343,7 @@ export function deduplicate(xs: Nums) { } export function indicesOf(a: Nums, b: Nums): Nums { - if (a === b) return ofSortedArray(createRangeArray(0, a.length - 1)); + if (areEqual(a, b)) return ofSortedArray(createRangeArray(0, a.length - 1)); const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b); let i = sI, j = sJ; diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts index d92f6066b02223e344a47829915f7b7d3c13654d..0d1b71197e62f0250942847adfcf3210d61dc6b5 100644 --- a/src/mol-data/int/sorted-array.ts +++ b/src/mol-data/int/sorted-array.ts @@ -17,6 +17,7 @@ namespace SortedArray { /** create sorted array [min, max) (it does NOT contain the max value) */ export const ofBounds: <T extends number = number>(min: T, max: T) => SortedArray<T> = (min, max) => Impl.ofRange(min, max - 1) as any; export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any; + export const isRange: <T extends number = number>(array: ArrayLike<number>) => boolean = Impl.isRange as any; export const has: <T extends number = number>(array: SortedArray<T>, x: T) => boolean = Impl.has as any; /** Returns the index of `x` in `set` or -1 if not found. */ diff --git a/src/mol-geo/geometry/marker-data.ts b/src/mol-geo/geometry/marker-data.ts index b22e0ce9deab5bf7cf102f98014246f3f62f114a..0a99554b5307d0e2e5fcff511fadf1fcf3c75281 100644 --- a/src/mol-geo/geometry/marker-data.ts +++ b/src/mol-geo/geometry/marker-data.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -9,20 +9,43 @@ import { Vec2 } from '../../mol-math/linear-algebra'; import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; export type MarkerData = { + uMarker: ValueCell<number>, tMarker: ValueCell<TextureImage<Uint8Array>> uMarkerTexDim: ValueCell<Vec2> + dMarkerType: ValueCell<string>, + markerAverage: ValueCell<number> + markerStatus: ValueCell<number> +} + +export function getMarkersAverage(array: Uint8Array, count: number): number { + if (count === 0) return 0; + let sum = 0; + for (let i = 0; i < count; ++i) { + sum += array[i] && 1; + } + return sum / count; } export function createMarkers(count: number, 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; if (markerData) { + ValueCell.updateIfChanged(markerData.uMarker, 0); ValueCell.update(markerData.tMarker, markers); ValueCell.update(markerData.uMarkerTexDim, Vec2.create(markers.width, markers.height)); + ValueCell.updateIfChanged(markerData.dMarkerType, status === -1 ? 'groupInstance' : 'uniform'); + ValueCell.updateIfChanged(markerData.markerAverage, average); + ValueCell.updateIfChanged(markerData.markerStatus, status); return markerData; } else { return { + uMarker: ValueCell.create(0), tMarker: ValueCell.create(markers), uMarkerTexDim: ValueCell.create(Vec2.create(markers.width, markers.height)), + markerAverage: ValueCell.create(average), + markerStatus: ValueCell.create(status), + dMarkerType: ValueCell.create('uniform'), }; } } @@ -30,13 +53,21 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat const emptyMarkerTexture = { array: new Uint8Array(1), width: 1, height: 1 }; export function createEmptyMarkers(markerData?: MarkerData): MarkerData { if (markerData) { + ValueCell.updateIfChanged(markerData.uMarker, 0); ValueCell.update(markerData.tMarker, emptyMarkerTexture); ValueCell.update(markerData.uMarkerTexDim, Vec2.create(1, 1)); + ValueCell.updateIfChanged(markerData.dMarkerType, 'uniform'); + ValueCell.updateIfChanged(markerData.markerAverage, 0); + ValueCell.updateIfChanged(markerData.markerStatus, 0); return markerData; } else { return { + uMarker: ValueCell.create(0), tMarker: ValueCell.create(emptyMarkerTexture), uMarkerTexDim: ValueCell.create(Vec2.create(1, 1)), + markerAverage: ValueCell.create(0), + markerStatus: ValueCell.create(0), + dMarkerType: ValueCell.create('uniform'), }; } } \ No newline at end of file diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index b2bcd93b1d4db2a733c713b402d8becef4d07042..3f1ba5f25f87423e4a3d6ffe3c20f2561aa5fb6d 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -134,17 +134,17 @@ describe('renderer', () => { scene.commit(); expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5); expect(ctx.stats.resourceCounts.texture).toBe(7); - expect(ctx.stats.resourceCounts.vertexArray).toBe(6); - expect(ctx.stats.resourceCounts.program).toBe(6); - expect(ctx.stats.resourceCounts.shader).toBe(12); + expect(ctx.stats.resourceCounts.vertexArray).toBe(8); + expect(ctx.stats.resourceCounts.program).toBe(8); + expect(ctx.stats.resourceCounts.shader).toBe(16); scene.remove(points); scene.commit(); expect(ctx.stats.resourceCounts.attribute).toBe(0); expect(ctx.stats.resourceCounts.texture).toBe(0); expect(ctx.stats.resourceCounts.vertexArray).toBe(0); - expect(ctx.stats.resourceCounts.program).toBe(6); - expect(ctx.stats.resourceCounts.shader).toBe(12); + expect(ctx.stats.resourceCounts.program).toBe(8); + expect(ctx.stats.resourceCounts.shader).toBe(16); ctx.resources.destroy(); expect(ctx.stats.resourceCounts.program).toBe(0); diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 9c8686e761b33128ebcb05609ded33c39416cc81..e9b486a936b8c272dab33c352f867f0cd0443b71 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -106,7 +106,6 @@ export type RenderableSchema = { } export type RenderableValues = { readonly [k: string]: ValueCell<any> } - // export const GlobalUniformSchema = { @@ -161,10 +160,13 @@ export const GlobalUniformSchema = { uHighlightColor: UniformSpec('v3'), uSelectColor: UniformSpec('v3'), + uHighlightStrength: UniformSpec('f'), + uSelectStrength: UniformSpec('f'), uXrayEdgeFalloff: UniformSpec('f'), uRenderWboit: UniformSpec('b'), + uMarkingDepthTest: UniformSpec('b'), } as const; export type GlobalUniformSchema = typeof GlobalUniformSchema export type GlobalUniformValues = Values<GlobalUniformSchema> @@ -208,8 +210,12 @@ export type SizeSchema = typeof SizeSchema export type SizeValues = Values<SizeSchema> export const MarkerSchema = { + uMarker: UniformSpec('f'), uMarkerTexDim: UniformSpec('v2'), tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), + dMarkerType: DefineSpec('string', ['uniform', 'groupInstance']), + markerAverage: ValueSpec('number'), + markerStatus: ValueSpec('number'), } as const; export type MarkerSchema = typeof MarkerSchema export type MarkerValues = Values<MarkerSchema> diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 372a4815debdfa06651fcc2f47016c019d42e0ff..82991710f04d66d8519a509f990074ef374a939e 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -48,6 +48,8 @@ interface Renderer { renderPick: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null) => void renderDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderMarkingDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderMarkingMask: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void @@ -76,6 +78,8 @@ export const RendererParams = { highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)), selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)), + highlightStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }), + selectStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }), xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }), @@ -242,6 +246,7 @@ namespace Renderer { uFogColor: ValueCell.create(bgColor), uRenderWboit: ValueCell.create(false), + uMarkingDepthTest: ValueCell.create(false), uTransparentBackground: ValueCell.create(false), @@ -267,6 +272,8 @@ namespace Renderer { uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)), uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)), + uHighlightStrength: ValueCell.create(p.highlightStrength), + uSelectStrength: ValueCell.create(p.selectStrength), uXrayEdgeFalloff: ValueCell.create(p.xrayEdgeFalloff), }; @@ -375,7 +382,7 @@ namespace Renderer { ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground); }; - const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean) => { + const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean, markingDepthTest: boolean) => { arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture); ValueCell.update(globalUniforms.uModel, group.view); @@ -385,6 +392,7 @@ namespace Renderer { ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection)); ValueCell.updateIfChanged(globalUniforms.uRenderWboit, renderWboit); + ValueCell.updateIfChanged(globalUniforms.uMarkingDepthTest, markingDepthTest); state.enable(gl.SCISSOR_TEST); state.colorMask(true, true, true, true); @@ -402,7 +410,7 @@ namespace Renderer { state.enable(gl.DEPTH_TEST); state.depthMask(true); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { @@ -417,7 +425,7 @@ namespace Renderer { state.enable(gl.DEPTH_TEST); state.depthMask(true); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { @@ -425,6 +433,40 @@ namespace Renderer { } }; + const renderMarkingDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + state.disable(gl.BLEND); + state.enable(gl.DEPTH_TEST); + state.depthMask(true); + + updateInternal(group, camera, depthTexture, false, false); + + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + + if (r.values.markerAverage.ref.value !== 1) { + renderObject(renderables[i], 'markingDepth'); + } + } + }; + + const renderMarkingMask = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + state.disable(gl.BLEND); + state.enable(gl.DEPTH_TEST); + state.depthMask(true); + + updateInternal(group, camera, depthTexture, false, !!depthTexture); + + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + + if (r.values.markerAverage.ref.value > 0) { + renderObject(renderables[i], 'markingMask'); + } + } + }; + const renderBlended = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { renderBlendedOpaque(group, camera, depthTexture); renderBlendedTransparent(group, camera, depthTexture); @@ -435,7 +477,7 @@ namespace Renderer { state.enable(gl.DEPTH_TEST); state.depthMask(true); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { @@ -449,7 +491,7 @@ namespace Renderer { const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { state.enable(gl.DEPTH_TEST); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; @@ -481,13 +523,13 @@ namespace Renderer { state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); state.enable(gl.BLEND); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - // TODO: simplify, handle on renderable.state??? + // TODO: simplify, handle in renderable.state??? // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && !r.values.dXrayShaded?.ref.value) { @@ -500,13 +542,13 @@ namespace Renderer { state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); state.enable(gl.BLEND); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - // TODO: simplify, handle on renderable.state??? + // TODO: simplify, handle in renderable.state??? // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value) { @@ -520,13 +562,13 @@ namespace Renderer { state.enable(gl.DEPTH_TEST); state.depthMask(true); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - // TODO: simplify, handle on renderable.state??? + // TODO: simplify, handle in renderable.state??? // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) { @@ -536,13 +578,13 @@ namespace Renderer { }; const renderWboitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { - updateInternal(group, camera, depthTexture, true); + updateInternal(group, camera, depthTexture, true, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - // TODO: simplify, handle on renderable.state??? + // TODO: simplify, handle in renderable.state??? // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) { @@ -577,6 +619,8 @@ namespace Renderer { renderPick, renderDepth, + renderMarkingDepth, + renderMarkingMask, renderBlended, renderBlendedOpaque, renderBlendedTransparent, @@ -618,6 +662,14 @@ namespace Renderer { p.selectColor = props.selectColor; ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor)); } + if (props.highlightStrength !== undefined && props.highlightStrength !== p.highlightStrength) { + p.highlightStrength = props.highlightStrength; + ValueCell.update(globalUniforms.uHighlightStrength, p.highlightStrength); + } + if (props.selectStrength !== undefined && props.selectStrength !== p.selectStrength) { + p.selectStrength = props.selectStrength; + ValueCell.update(globalUniforms.uSelectStrength, p.selectStrength); + } if (props.xrayEdgeFalloff !== undefined && props.xrayEdgeFalloff !== p.xrayEdgeFalloff) { p.xrayEdgeFalloff = props.xrayEdgeFalloff; diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 153f2043cce58ab9186731e745d0ca22a5ef1ef6..2e507084f76ec4e38d1b7da0df09d9b60c509dda 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -65,7 +65,6 @@ import { size_vert_params } from './shader/chunks/size-vert-params.glsl'; import { texture3d_from_1d_trilinear } from './shader/chunks/texture3d-from-1d-trilinear.glsl'; import { texture3d_from_2d_linear } from './shader/chunks/texture3d-from-2d-linear.glsl'; import { texture3d_from_2d_nearest } from './shader/chunks/texture3d-from-2d-nearest.glsl'; -import { wboit_params } from './shader/chunks/wboit-params.glsl'; import { wboit_write } from './shader/chunks/wboit-write.glsl'; const ShaderChunks: { [k: string]: string } = { @@ -99,7 +98,6 @@ const ShaderChunks: { [k: string]: string } = { texture3d_from_1d_trilinear, texture3d_from_2d_linear, texture3d_from_2d_nearest, - wboit_params, wboit_write }; diff --git a/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts b/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts index ad121dcc8ba6932be66346ccb1c617989c1b52e4..9b888c7493cab936e2fddc26a84258b3efe49482 100644 --- a/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts +++ b/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts @@ -1,11 +1,11 @@ export const apply_marker_color = ` -float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win if (marker > 0.1) { if (intMod(marker, 2.0) > 0.1) { - gl_FragColor.rgb = mix(uHighlightColor, gl_FragColor.rgb, 0.3); - gl_FragColor.a = max(0.02, gl_FragColor.a); // for direct-volume rendering + gl_FragColor.rgb = mix(gl_FragColor.rgb, uHighlightColor, uHighlightStrength); + gl_FragColor.a = max(gl_FragColor.a, uHighlightStrength * 0.002); // for direct-volume rendering } else { - gl_FragColor.rgb = mix(uSelectColor, gl_FragColor.rgb, 0.3); + gl_FragColor.rgb = mix(gl_FragColor.rgb, uSelectColor, uSelectStrength); + gl_FragColor.a = max(gl_FragColor.a, uSelectStrength * 0.002); // for direct-volume rendering } } `; \ No newline at end of file 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 0608fca27d3a38e6fbb785ea8e64e1588671adad..22442c2da62b8cb9a732a0a1ebf52b0528821300 100644 --- a/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts @@ -1,3 +1,5 @@ export const assign_marker_varying = ` -vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a; +#if defined(dMarkerType_groupInstance) + vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a; +#endif `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts index 247a2b6caf1a7dcbdea2a060237bd47a70db920e..c147051ec483c6b642b3d0a3af4c4ad224b9634d 100644 --- a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts @@ -1,4 +1,13 @@ export const assign_material_color = ` +#if defined(dRenderVariant_color) || defined(dRenderVariant_marking) + #if defined(dMarkerType_uniform) + float marker = uMarker; + #elif defined(dMarkerType_groupInstance) + float marker = vMarker; + #endif + marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win +#endif + #if defined(dRenderVariant_color) #if defined(dUsePalette) vec4 material = vec4(texture2D(tPalette, vec2(vPaletteV, 0.5)).rgb, uAlpha); @@ -20,6 +29,27 @@ export const assign_material_color = ` #else vec4 material = packDepthToRGBA(gl_FragCoord.z); #endif +#elif defined(dRenderVariant_markingDepth) + if (marker > 0.0) + discard; + #ifdef enabledFragDepth + vec4 material = packDepthToRGBA(gl_FragDepthEXT); + #else + vec4 material = packDepthToRGBA(gl_FragCoord.z); + #endif +#elif defined(dRenderVariant_markingMask) + if (marker == 0.0) + discard; + float depthTest = 1.0; + if (uMarkingDepthTest) { + depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0; + } + bool isHighlight = intMod(marker, 2.0) > 0.1; + float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar); + float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ)); + if (fogFactor == 1.0) + discard; + vec4 material = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0 - fogFactor); #endif // apply screendoor transparency diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts index 416535c821c5cdc39cbf7b936ae9220b32615852..e3aac5f0e61d0a1740385f21a90a9beaabc95a2e 100644 --- a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts +++ b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts @@ -21,10 +21,17 @@ uniform int uGroupCount; uniform vec3 uHighlightColor; uniform vec3 uSelectColor; -#if __VERSION__ == 100 - varying float vMarker; -#else - flat in float vMarker; +uniform float uHighlightStrength; +uniform float uSelectStrength; + +#if defined(dMarkerType_uniform) + uniform float uMarker; +#elif defined(dMarkerType_groupInstance) + #if __VERSION__ == 100 + varying float vMarker; + #else + flat in float vMarker; + #endif #endif varying vec3 vModelPosition; @@ -52,4 +59,20 @@ bool interior; uniform float uXrayEdgeFalloff; uniform mat4 uProjection; + +uniform bool uRenderWboit; +uniform bool uMarkingDepthTest; + +uniform sampler2D tDepth; +uniform vec2 uDrawingBufferSize; + +float getDepth(const in vec2 coords) { + // always packed due to merged depth from primitives and volumes + return unpackRGBAToDepth(texture2D(tDepth, coords)); +} + +float calcDepth(const in vec3 pos) { + vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw; + return 0.5 + 0.5 * clipZW.x / clipZW.y; +} `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-vert-params.glsl.ts b/src/mol-gl/shader/chunks/common-vert-params.glsl.ts index 8aaa7f39a3ff6a5b296b4503969debff1e08e03e..87bc9792286756131946134dc94ef6a413694f59 100644 --- a/src/mol-gl/shader/chunks/common-vert-params.glsl.ts +++ b/src/mol-gl/shader/chunks/common-vert-params.glsl.ts @@ -26,12 +26,16 @@ uniform vec4 uInvariantBoundingSphere; #endif #endif -uniform vec2 uMarkerTexDim; -uniform sampler2D tMarker; -#if __VERSION__ == 100 - varying float vMarker; -#else - flat out float vMarker; +#if defined(dMarkerType_uniform) + uniform float uMarker; +#elif defined(dMarkerType_groupInstance) + uniform vec2 uMarkerTexDim; + uniform sampler2D tMarker; + #if __VERSION__ == 100 + varying float vMarker; + #else + flat out float vMarker; + #endif #endif varying vec3 vModelPosition; diff --git a/src/mol-gl/shader/chunks/common.glsl.ts b/src/mol-gl/shader/chunks/common.glsl.ts index 14296e294232baa234bed597d814cc8dc23c28f1..e67f2d784bf1f387e206104e039e4fd984350279 100644 --- a/src/mol-gl/shader/chunks/common.glsl.ts +++ b/src/mol-gl/shader/chunks/common.glsl.ts @@ -9,6 +9,10 @@ export const common = ` #define dRenderVariant_pick #endif +#if defined(dRenderVariant_markingDepth) || defined(dRenderVariant_markingMask) + #define dRenderVariant_marking +#endif + #if defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance) || defined(dColorType_vertex) || defined(dColorType_vertexInstance) #define dColorType_texture #endif diff --git a/src/mol-gl/shader/chunks/wboit-params.glsl.ts b/src/mol-gl/shader/chunks/wboit-params.glsl.ts deleted file mode 100644 index 53b070225961389665f538ec36573dcef6374b3a..0000000000000000000000000000000000000000 --- a/src/mol-gl/shader/chunks/wboit-params.glsl.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const wboit_params = ` -#if defined(dRenderVariant_colorWboit) - #if !defined(dRenderMode_volume) && !defined(dRenderMode_isosurface) - uniform sampler2D tDepth; - uniform vec2 uDrawingBufferSize; - - float getDepth(const in vec2 coords) { - // always packed due to merged depth from primitives and volumes - return unpackRGBAToDepth(texture2D(tDepth, coords)); - } - #endif -#endif - -uniform bool uRenderWboit; - -float calcDepth(const in vec3 pos) { - vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw; - return 0.5 + 0.5 * clipZW.x / clipZW.y; -} -`; \ No newline at end of file diff --git a/src/mol-gl/shader/cylinders.frag.ts b/src/mol-gl/shader/cylinders.frag.ts index aeedc563e2dae9fb6b062c6c3a78cb3458fe721a..6efcca93fee3f2fbfa581107d46df2b362ed1338 100644 --- a/src/mol-gl/shader/cylinders.frag.ts +++ b/src/mol-gl/shader/cylinders.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -24,7 +24,6 @@ uniform vec3 uCameraPosition; #include color_frag_params #include light_frag_params #include common_clip -#include wboit_params // adapted from https://www.shadertoy.com/view/4lcSRn // The MIT License, Copyright 2016 Inigo Quilez @@ -121,6 +120,8 @@ void main() { gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) #ifdef dIgnoreLight gl_FragColor = material; diff --git a/src/mol-gl/shader/direct-volume.frag.ts b/src/mol-gl/shader/direct-volume.frag.ts index 98649b770cb7222b627a0d1510be8220065dd1c4..6611631063258c21475ccd630f82e8304c33f409 100644 --- a/src/mol-gl/shader/direct-volume.frag.ts +++ b/src/mol-gl/shader/direct-volume.frag.ts @@ -53,8 +53,15 @@ uniform int uGroupCount; uniform vec3 uHighlightColor; uniform vec3 uSelectColor; -uniform vec2 uMarkerTexDim; -uniform sampler2D tMarker; +uniform float uHighlightStrength; +uniform float uSelectStrength; + +#if defined(dMarkerType_uniform) + uniform float uMarker; +#elif defined(dMarkerType_groupInstance) + uniform vec2 uMarkerTexDim; + uniform sampler2D tMarker; +#endif uniform float uFogNear; uniform float uFogFar; @@ -69,6 +76,8 @@ uniform bool uInteriorColorFlag; uniform vec3 uInteriorColor; bool interior; +uniform bool uRenderWboit; + uniform float uNear; uniform float uFar; uniform float uIsOrtho; @@ -122,7 +131,10 @@ uniform mat4 uCartnToUnit; } #endif -#include wboit_params +float calcDepth(const in vec3 pos) { + vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw; + return 0.5 + 0.5 * clipZW.x / clipZW.y; +} vec4 transferFunction(float value) { return texture2D(tTransferTex, vec2(value, 0.0)); @@ -322,7 +334,12 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) { #include apply_light_color #endif - float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; + #if defined(dMarkerType_uniform) + float marker = uMarker; + #elif defined(dMarkerType_groupInstance) + float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; + marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win + #endif #include apply_interior_color #include apply_marker_color @@ -385,14 +402,18 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) { gl_FragColor.a = material.a * uAlpha * uTransferScale; - #ifdef dPackedGroup - float group = decodeFloatRGB(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb); - #else - vec3 g = floor(unitPos * uGridDim + 0.5); - float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y; + #if defined(dMarkerType_uniform) + float marker = uMarker; + #elif defined(dMarkerType_groupInstance) + #ifdef dPackedGroup + float group = decodeFloatRGB(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb); + #else + vec3 g = floor(unitPos * uGridDim + 0.5); + float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y; + #endif + float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; + marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win #endif - - float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; #include apply_marker_color preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended; @@ -432,6 +453,11 @@ void main() { if (gl_FrontFacing) discard; + #ifdef dRenderVariant_marking + // not supported + discard; + #endif + #if defined(dRenderVariant_pick) || defined(dRenderVariant_depth) #if defined(dRenderMode_volume) // always ignore pick & depth for volume diff --git a/src/mol-gl/shader/image.frag.ts b/src/mol-gl/shader/image.frag.ts index f6e26255b178e1039a7f48ba5deb65a718d5f859..175c65fd933e96ee6334a8dc2b06b080dc1af9ae 100644 --- a/src/mol-gl/shader/image.frag.ts +++ b/src/mol-gl/shader/image.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -11,7 +11,6 @@ precision highp int; #include read_from_texture #include common_frag_params #include common_clip -#include wboit_params uniform vec2 uImageTexDim; uniform sampler2D tImageTex; @@ -105,7 +104,6 @@ void main() { #if defined(dRenderVariant_pick) if (imageData.a < 0.3) discard; - #if defined(dRenderVariant_pickObject) gl_FragColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0); #elif defined(dRenderVariant_pickInstance) @@ -116,17 +114,42 @@ void main() { #elif defined(dRenderVariant_depth) if (imageData.a < 0.05) discard; - gl_FragColor = packDepthToRGBA(gl_FragCoord.z); + #elif defined(dRenderVariant_marking) + #if defined(dMarkerType_uniform) + float marker = uMarker; + #elif defined(dMarkerType_groupInstance) + float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb); + float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; + marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win + #endif + #if defined(dRenderVariant_markingDepth) + if (marker > 0.0 || imageData.a < 0.05) + discard; + gl_FragColor = packDepthToRGBA(gl_FragCoord.z); + #elif defined(dRenderVariant_markingMask) + if (marker == 0.0 || imageData.a < 0.05) + discard; + float depthTest = 1.0; + if (uMarkingDepthTest) { + depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0; + } + bool isHighlight = intMod(marker, 2.0) > 0.1; + gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0); + #endif #elif defined(dRenderVariant_color) if (imageData.a < 0.05) discard; - gl_FragColor = imageData; gl_FragColor.a *= uAlpha; - float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb); - float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; + #if defined(dMarkerType_uniform) + float marker = uMarker; + #elif defined(dMarkerType_groupInstance) + float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb); + float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; + marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win + #endif #include apply_marker_color #include apply_fog diff --git a/src/mol-gl/shader/lines.frag.ts b/src/mol-gl/shader/lines.frag.ts index bc509c14896d4a53d005a88e2c38e400927bd513..1340a5efa621680908e3f9f2587f7f5703427c4a 100644 --- a/src/mol-gl/shader/lines.frag.ts +++ b/src/mol-gl/shader/lines.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -12,7 +12,6 @@ precision highp int; #include common_frag_params #include color_frag_params #include common_clip -#include wboit_params void main(){ #include clip_pixel @@ -26,6 +25,8 @@ void main(){ gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) gl_FragColor = material; diff --git a/src/mol-gl/shader/marking/edge.frag.ts b/src/mol-gl/shader/marking/edge.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..637b8533bdce8c5c407ab7fea802e92fafab32ec --- /dev/null +++ b/src/mol-gl/shader/marking/edge.frag.ts @@ -0,0 +1,29 @@ +export const edge_frag = ` +precision highp float; +precision highp sampler2D; + +uniform sampler2D tMaskTexture; +uniform vec2 uTexSizeInv; + +void main() { + vec2 coords = gl_FragCoord.xy * uTexSizeInv; + vec4 offset = vec4(float(dEdgeScale), 0.0, 0.0, float(dEdgeScale)) * vec4(uTexSizeInv, uTexSizeInv); + vec4 c0 = texture2D(tMaskTexture, coords); + vec4 c1 = texture2D(tMaskTexture, coords + offset.xy); + vec4 c2 = texture2D(tMaskTexture, coords - offset.xy); + vec4 c3 = texture2D(tMaskTexture, coords + offset.yw); + vec4 c4 = texture2D(tMaskTexture, coords - offset.yw); + float diff1 = (c1.r - c2.r) * 0.5; + float diff2 = (c3.r - c4.r) * 0.5; + float d = length(vec2(diff1, diff2)); + if (d <= 0.0) + discard; + float a1 = min(c1.g, c2.g); + float a2 = min(c3.g, c4.g); + float visibility = min(a1, a2) > 0.001 ? 1.0 : 0.0; + float mask = c0.r; + float marker = min(c1.b, min(c2.b, min(c3.b, c4.b))); + float fogAlpha = min(c1.a, min(c2.a, min(c3.a, c4.a))); + gl_FragColor = vec4(visibility, mask, marker, fogAlpha); +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/marking/overlay.frag.ts b/src/mol-gl/shader/marking/overlay.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ba04ce009352c4e4148e7d57935ea502ff31644 --- /dev/null +++ b/src/mol-gl/shader/marking/overlay.frag.ts @@ -0,0 +1,23 @@ +export const overlay_frag = ` +precision highp float; +precision highp sampler2D; + +uniform vec2 uTexSizeInv; +uniform sampler2D tEdgeTexture; +uniform vec3 uHighlightEdgeColor; +uniform vec3 uSelectEdgeColor; +uniform float uGhostEdgeStrength; +uniform float uInnerEdgeFactor; + +void main() { + vec2 coords = gl_FragCoord.xy * uTexSizeInv; + vec4 edgeValue = texture2D(tEdgeTexture, coords); + if (edgeValue.a > 0.0) { + vec3 edgeColor = edgeValue.b == 1.0 ? uHighlightEdgeColor : uSelectEdgeColor; + gl_FragColor.rgb = edgeValue.g > 0.0 ? edgeColor : edgeColor * uInnerEdgeFactor; + gl_FragColor.a = (edgeValue.r == 1.0 ? uGhostEdgeStrength : 1.0) * edgeValue.a; + } else { + gl_FragColor = vec4(0.0); + } +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.frag.ts b/src/mol-gl/shader/mesh.frag.ts index d4f99f4715889852fd538e1f7bdc7b7a1a717daf..987a68e53778258dab1ec15e311a823b44b1a666 100644 --- a/src/mol-gl/shader/mesh.frag.ts +++ b/src/mol-gl/shader/mesh.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -14,7 +14,6 @@ precision highp int; #include light_frag_params #include normal_frag_params #include common_clip -#include wboit_params void main() { #include clip_pixel @@ -43,6 +42,8 @@ void main() { gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) #ifdef dIgnoreLight gl_FragColor = material; diff --git a/src/mol-gl/shader/points.frag.ts b/src/mol-gl/shader/points.frag.ts index 5328d5aeee378fa66c45eeda3d7859e7c8f9a913..c18506882ebd89581df63f96a0474c9fa28718f8 100644 --- a/src/mol-gl/shader/points.frag.ts +++ b/src/mol-gl/shader/points.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -12,7 +12,6 @@ precision highp int; #include common_frag_params #include color_frag_params #include common_clip -#include wboit_params #ifdef dPointFilledCircle uniform float uPointEdgeBleach; @@ -33,6 +32,8 @@ void main(){ gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) gl_FragColor = material; diff --git a/src/mol-gl/shader/spheres.frag.ts b/src/mol-gl/shader/spheres.frag.ts index 72d51ddd076fc9f5e14fe56d77e48ce7cf3d8e85..47f3c63bd70a88824f959b612f6ed4831d2ba5f4 100644 --- a/src/mol-gl/shader/spheres.frag.ts +++ b/src/mol-gl/shader/spheres.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -13,7 +13,6 @@ precision highp int; #include color_frag_params #include light_frag_params #include common_clip -#include wboit_params varying float vRadius; varying float vRadiusSq; @@ -86,6 +85,8 @@ void main(void){ gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) #ifdef dIgnoreLight gl_FragColor = material; diff --git a/src/mol-gl/shader/text.frag.ts b/src/mol-gl/shader/text.frag.ts index 807282c101900be203cf36b94df74afb8f25539f..390886e55eeed3945290e86b1732727ab04cb960 100644 --- a/src/mol-gl/shader/text.frag.ts +++ b/src/mol-gl/shader/text.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -12,7 +12,6 @@ precision highp int; #include common_frag_params #include color_frag_params #include common_clip -#include wboit_params uniform sampler2D tFont; @@ -66,6 +65,8 @@ void main(){ #include check_picking_alpha #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) #include apply_marker_color #include apply_fog diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index bcbfca4dc5452f42c967102b71698165e20c6c14..c9086c9a603c7e809db1f694c5152f6561c24b2a 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -49,7 +49,7 @@ export interface RenderItem<T extends string> { // -const GraphicsRenderVariant = { 'colorBlended': '', 'colorWboit': '', 'pickObject': '', 'pickInstance': '', 'pickGroup': '', 'depth': '' }; +const GraphicsRenderVariant = { 'colorBlended': '', 'colorWboit': '', 'pickObject': '', 'pickInstance': '', 'pickGroup': '', 'depth': '', 'markingDepth': '', 'markingMask': '' }; export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[]; diff --git a/src/mol-model/structure/structure/element/loci.ts b/src/mol-model/structure/structure/element/loci.ts index 654c357fcf77ddd2e0d65664eac721b7b6b29961..2bd638ec668b1e94bbf66b29240ace09ef8f2fde 100644 --- a/src/mol-model/structure/structure/element/loci.ts +++ b/src/mol-model/structure/structure/element/loci.ts @@ -66,7 +66,10 @@ export namespace Loci { } export function isEmpty(loci: Loci) { - return size(loci) === 0; + for (const u of loci.elements) { + if(OrderedSet.size(u.indices) > 0) return false; + } + return true; } export function isWholeStructure(loci: Loci) { @@ -140,7 +143,7 @@ export namespace Loci { return Structure.create(units, { parent: loci.structure.parent }); } - // TODO: there should be a version that property supports partitioned units + // TODO: there should be a version that properly supports partitioned units export function remap(loci: Loci, structure: Structure): Loci { if (structure === loci.structure) return loci; @@ -250,6 +253,14 @@ export namespace Loci { return isSubset; } + function makeIndexSet(newIndices: ArrayLike<UnitIndex>): OrderedSet<UnitIndex> { + if (newIndices.length > 3 && SortedArray.isRange(newIndices)) { + return Interval.ofRange(newIndices[0], newIndices[newIndices.length - 1]); + } else { + return SortedArray.ofSortedArray(newIndices); + } + } + export function extendToWholeResidues(loci: Loci, restrictToConformation?: boolean): Loci { const elements: Loci['elements'][0][] = []; const residueAltIds = new Set<string>(); @@ -294,7 +305,7 @@ export namespace Loci { } } - elements[elements.length] = { unit: lociElement.unit, indices: SortedArray.ofSortedArray(newIndices) }; + elements[elements.length] = { unit: lociElement.unit, indices: makeIndexSet(newIndices) }; } else { // coarse elements are already by-residue elements[elements.length] = lociElement; @@ -316,14 +327,6 @@ export namespace Loci { return element.unit.elements.length === OrderedSet.size(element.indices); } - function makeIndexSet(newIndices: number[]): OrderedSet<UnitIndex> { - if (newIndices.length > 12 && newIndices[newIndices.length - 1] - newIndices[0] === newIndices.length - 1) { - return Interval.ofRange(newIndices[0], newIndices[newIndices.length - 1]); - } else { - return SortedArray.ofSortedArray(newIndices); - } - } - function collectChains(unit: Unit, chainIndices: Set<ChainIndex>, elements: Loci['elements'][0][]) { const { index } = getChainSegments(unit); const xs = unit.elements; @@ -467,7 +470,10 @@ export namespace Loci { } function getUnitIndices(elements: SortedArray<ElementIndex>, indices: SortedArray<ElementIndex>) { - return SortedArray.indicesOf<ElementIndex, UnitIndex>(elements, indices); + if (SortedArray.isRange(elements) && SortedArray.areEqual(elements, indices)) { + return Interval.ofLength(elements.length); + } + return makeIndexSet(SortedArray.indicesOf<ElementIndex, UnitIndex>(elements, indices)); } export function extendToAllInstances(loci: Loci): Loci { diff --git a/src/mol-plugin/util/viewport-screenshot.ts b/src/mol-plugin/util/viewport-screenshot.ts index f24a6d4b1a5a688abf4e71d50d20515951908baa..b1454feb1e7ac3c5b87a182c1d266325045fe5ba 100644 --- a/src/mol-plugin/util/viewport-screenshot.ts +++ b/src/mol-plugin/util/viewport-screenshot.ts @@ -1,11 +1,11 @@ -import { Viewport } from '../../mol-canvas3d/camera/util'; /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import { Viewport } from '../../mol-canvas3d/camera/util'; import { CameraHelperParams } from '../../mol-canvas3d/helper/camera-helper'; import { ImagePass } from '../../mol-canvas3d/passes/image'; import { canvasToBlob } from '../../mol-canvas3d/util'; @@ -108,8 +108,8 @@ class ViewportScreenshotHelper extends PluginComponent { private createPass(mutlisample: boolean) { const c = this.plugin.canvas3d!; const { colorBufferFloat, textureFloat } = c.webgl.extensions; - const aoProps = this.plugin.canvas3d!.props.postprocessing.occlusion; - return this.plugin.canvas3d!.getImagePass({ + const aoProps = c.props.postprocessing.occlusion; + return c.getImagePass({ transparentBackground: this.values.transparent, cameraHelper: { axes: this.values.axes }, multiSample: { @@ -121,7 +121,8 @@ class ViewportScreenshotHelper extends PluginComponent { occlusion: aoProps.name === 'on' ? { name: 'on', params: { ...aoProps.params, samples: 128 } } : aoProps - } + }, + marking: { ...c.props.marking } }); } @@ -133,17 +134,19 @@ class ViewportScreenshotHelper extends PluginComponent { private _imagePass: ImagePass; get imagePass() { if (this._imagePass) { - const aoProps = this.plugin.canvas3d!.props.postprocessing.occlusion; + const c = this.plugin.canvas3d!; + const aoProps = c.props.postprocessing.occlusion; this._imagePass.setProps({ cameraHelper: { axes: this.values.axes }, transparentBackground: this.values.transparent, // TODO: optimize because this creates a copy of a large object! postprocessing: { - ...this.plugin.canvas3d!.props.postprocessing, + ...c.props.postprocessing, occlusion: aoProps.name === 'on' ? { name: 'on', params: { ...aoProps.params, samples: 128 } } : aoProps - } + }, + marking: { ...c.props.marking } }); return this._imagePass; } @@ -266,7 +269,8 @@ class ViewportScreenshotHelper extends PluginComponent { cameraHelper: { axes: this.values.axes }, transparentBackground: this.values.transparent, // TODO: optimize because this creates a copy of a large object! - postprocessing: canvasProps.postprocessing + postprocessing: canvasProps.postprocessing, + marking: canvasProps.marking }); const imageData = this.previewPass.getImageData(w, h); const canvas = this.previewCanvas; diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index 7dc4a1e654dbcee0b37ec0eea32c53c9024a8a00..8f503ace41d9dd8bb1dfafdea68a19a9d14b2041 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/src/mol-repr/structure/complex-visual.ts @@ -67,6 +67,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate, processValues, dispose } = builder; const { updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils; const updateState = VisualUpdateState.create(); + const previousMark: Visual.PreviousMark = { loci: EmptyLoci, action: MarkerAction.None, status: -1 }; let renderObject: GraphicsRenderObject<G['kind']> | undefined; @@ -235,7 +236,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge return renderObject ? getLoci(pickingId, currentStructure, renderObject.id) : EmptyLoci; }, mark(loci: Loci, action: MarkerAction) { - return Visual.mark(renderObject, loci, action, lociApply); + return Visual.mark(renderObject, loci, action, lociApply, previousMark); }, setVisibility(visible: boolean) { Visual.setVisibility(renderObject, visible); diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index fd28a68df566f079482e74d7e1741009849fc20c..e4105f9eb16589aefee9e2e547f7a5a3f2e38c67 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -71,6 +71,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate, processValues, dispose } = builder; const { createEmpty: createEmptyGeometry, updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils; const updateState = VisualUpdateState.create(); + const previousMark: Visual.PreviousMark = { loci: EmptyLoci, action: MarkerAction.None, status: -1 }; let renderObject: GraphicsRenderObject<G['kind']> | undefined; @@ -289,7 +290,18 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom return renderObject ? getLoci(pickingId, currentStructureGroup, renderObject.id) : EmptyLoci; }, mark(loci: Loci, action: MarkerAction) { - return Visual.mark(renderObject, loci, action, lociApply); + let hasInvariantId = true; + if (StructureElement.Loci.is(loci)) { + hasInvariantId = false; + const { invariantId } = currentStructureGroup.group.units[0]; + for (const e of loci.elements) { + if (e.unit.invariantId === invariantId) { + hasInvariantId = true; + break; + } + } + } + return hasInvariantId ? Visual.mark(renderObject, loci, action, lociApply, previousMark) : false; }, setVisibility(visible: boolean) { Visual.setVisibility(renderObject, visible); diff --git a/src/mol-repr/structure/visual/util/element.ts b/src/mol-repr/structure/visual/util/element.ts index b9a8ea919a3e669edfc8f0921d8456954bd33fe0..34ca57ffaf8d02daa31d2c1200c8cce4d513f6e5 100644 --- a/src/mol-repr/structure/visual/util/element.ts +++ b/src/mol-repr/structure/visual/util/element.ts @@ -164,8 +164,9 @@ export function eachElement(loci: Loci, structureGroup: StructureGroup, apply: ( const { structure, group } = structureGroup; if (!Structure.areEquivalent(loci.structure, structure)) return false; const elementCount = group.elements.length; + const { unitIndexMap } = group; for (const e of loci.elements) { - const unitIdx = group.unitIndexMap.get(e.unit.id); + const unitIdx = unitIndexMap.get(e.unit.id); if (unitIdx !== undefined) { const offset = unitIdx * elementCount; // to target unit instance if (Interval.is(e.indices)) { diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts index 5dcae09a358749195e291c4272ca050f93efb522..163a47dfaff3d1d5a1c3b37f86dd80b73039c92e 100644 --- a/src/mol-repr/visual.ts +++ b/src/mol-repr/visual.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -7,8 +7,8 @@ import { RuntimeContext } from '../mol-task'; import { GraphicsRenderObject } from '../mol-gl/render-object'; import { PickingId } from '../mol-geo/geometry/picking'; -import { Loci, isEmptyLoci, isEveryLoci } from '../mol-model/loci'; -import { MarkerAction, applyMarkerAction } from '../mol-util/marker-action'; +import { Loci, isEmptyLoci, isEveryLoci, EveryLoci } from '../mol-model/loci'; +import { MarkerAction, applyMarkerAction, getMarkerInfo, setMarkerValue, getPartialMarkerAverage, MarkerActions, MarkerInfo } from '../mol-util/marker-action'; import { ParamDefinition as PD } from '../mol-util/param-definition'; import { WebGLContext } from '../mol-gl/webgl/context'; import { Theme } from '../mol-theme/theme'; @@ -23,6 +23,7 @@ import { Transparency } from '../mol-theme/transparency'; import { createTransparency, clearTransparency, applyTransparencyValue, getTransparencyAverage } from '../mol-geo/geometry/transparency-data'; import { Clipping } from '../mol-theme/clipping'; import { createClipping, applyClippingGroups, clearClipping } from '../mol-geo/geometry/clipping-data'; +import { getMarkersAverage } from '../mol-geo/geometry/marker-data'; export interface VisualContext { readonly runtime: RuntimeContext @@ -67,20 +68,68 @@ namespace Visual { if (renderObject) renderObject.state.colorOnly = colorOnly; } - export function mark(renderObject: GraphicsRenderObject | undefined, loci: Loci, action: MarkerAction, lociApply: LociApply) { - if (!renderObject) return false; + export type PreviousMark = { loci: Loci, action: MarkerAction, status: MarkerInfo['status'] } - const { tMarker, uGroupCount, instanceCount } = renderObject.values; + export function mark(renderObject: GraphicsRenderObject | undefined, loci: Loci, action: MarkerAction, lociApply: LociApply, previous?: PreviousMark) { + if (!renderObject || isEmptyLoci(loci)) return false; + + const { tMarker, dMarkerType, uMarker, markerAverage, markerStatus, uGroupCount, instanceCount } = renderObject.values; const count = uGroupCount.ref.value * instanceCount.ref.value; const { array } = tMarker.ref.value; + const currentStatus = markerStatus.ref.value as MarkerInfo['status']; + + if (!isEveryLoci(loci)) { + let intervalSize = 0; + lociApply(loci, interval => { + intervalSize += Interval.size(interval); + return true; + }, true); + if (intervalSize === 0) return false; + if (intervalSize === count) loci = EveryLoci; + } let changed = false; + let average = -1; + let status: MarkerInfo['status'] = -1; if (isEveryLoci(loci)) { - changed = applyMarkerAction(array, Interval.ofLength(count), action); - } else if (!isEmptyLoci(loci)) { + const info = getMarkerInfo(action, currentStatus); + if (info.status !== -1) { + changed = currentStatus !== info.status; + if (changed) setMarkerValue(array, info.status, count); + } else { + changed = applyMarkerAction(array, Interval.ofLength(count), action); + } + average = info.average; + status = info.status; + } else { changed = lociApply(loci, interval => applyMarkerAction(array, interval, action), true); + if (changed) { + average = getPartialMarkerAverage(action, currentStatus); + if (previous && previous.status !== -1 && average === -1 && + MarkerActions.isReverse(previous.action, action) && + Loci.areEqual(loci, previous.loci) + ) { + status = previous.status; + average = status === 0 ? 0 : 0.5; + } + } + } + if (changed) { + if (average === -1) { + average = getMarkersAverage(array, count); + if (average === 0) status = 0; + } + if (previous) { + previous.action = action; + previous.loci = loci; + previous.status = currentStatus; + } + ValueCell.updateIfChanged(uMarker, status); + if (status === -1) ValueCell.update(tMarker, tMarker.ref.value); + ValueCell.updateIfChanged(dMarkerType, status === -1 ? 'groupInstance' : 'uniform'); + ValueCell.updateIfChanged(markerAverage, average); + ValueCell.updateIfChanged(markerStatus, status); } - if (changed) ValueCell.update(tMarker, tMarker.ref.value); return changed; } diff --git a/src/mol-util/marker-action.ts b/src/mol-util/marker-action.ts index fe87ae3806c190799e55c9b2da288947659129d7..25b5b1671501bfeee821009f907aa79235f0ea2b 100644 --- a/src/mol-util/marker-action.ts +++ b/src/mol-util/marker-action.ts @@ -35,6 +35,20 @@ export namespace MarkerActions { MarkerAction.Select | MarkerAction.Deselect | MarkerAction.Toggle | MarkerAction.Clear ) as MarkerActions; + + export function isReverse(a: MarkerAction, b: MarkerAction) { + return ( + (a === MarkerAction.Highlight && b === MarkerAction.RemoveHighlight) || + (a === MarkerAction.RemoveHighlight && b === MarkerAction.Highlight) || + (a === MarkerAction.Select && b === MarkerAction.Deselect) || + (a === MarkerAction.Deselect && b === MarkerAction.Select) || + (a === MarkerAction.Toggle && b === MarkerAction.Toggle) + ); + } +} + +export function setMarkerValue(array: Uint8Array, status: 0 | 1 | 2 | 3, count: number) { + array.fill(status, 0, count); } export function applyMarkerActionAtPosition(array: Uint8Array, i: number, action: MarkerAction) { @@ -120,3 +134,135 @@ export function applyMarkerAction(array: Uint8Array, set: OrderedSet, action: Ma } return true; } + + +export interface MarkerInfo { + /** + * 0: none marked; + * 1: all marked; + * -1: unclear, need to be calculated + */ + average: 0 | 1 | -1 + /** + * 0: none marked; + * 1: all highlighted; + * 2: all selected; + * 3: all highlighted and selected + * -1: mixed/unclear + */ + status: 0 | 1 | 2 | 3 | -1 +} + +export function getMarkerInfo(action: MarkerAction, currentStatus: MarkerInfo['status']): MarkerInfo { + let average: MarkerInfo['average'] = -1; + let status: MarkerInfo['status'] = -1; + switch (action) { + case MarkerAction.Highlight: + if (currentStatus === 0 || currentStatus === 1) { + average = 1; + status = 1; + } else if (currentStatus === 2 || currentStatus === 3) { + average = 1; + status = 3; + } else { + average = 1; + } + break; + case MarkerAction.RemoveHighlight: + if (currentStatus === 0 || currentStatus === 1) { + average = 0; + status = 0; + } else if (currentStatus === 2 || currentStatus === 3) { + average = 1; + status = 2; + } + break; + case MarkerAction.Select: + if (currentStatus === 1 || currentStatus === 3) { + average = 1; + status = 3; + } else if (currentStatus === 0 || currentStatus === 2) { + average = 1; + status = 2; + } else { + average = 1; + } + break; + case MarkerAction.Deselect: + if (currentStatus === 1 || currentStatus === 3) { + average = 1; + status = 1; + } else if (currentStatus === 0 || currentStatus === 2) { + average = 0; + status = 0; + } + break; + case MarkerAction.Toggle: + if (currentStatus === 1) { + average = 1; + status = 3; + } else if (currentStatus === 2) { + average = 0; + status = 0; + } else if (currentStatus === 3) { + average = 1; + status = 1; + } else if (currentStatus === 0) { + average = 1; + status = 2; + } + break; + case MarkerAction.Clear: + average = 0; + status = 0; + break; + } + return { average, status }; +} + +/** + * Assumes the action is applied to a partial set that is + * neither the empty set nor the full set. + */ +export function getPartialMarkerAverage(action: MarkerAction, currentStatus: MarkerInfo['status']) { + switch (action) { + case MarkerAction.Highlight: + return 0.5; + case MarkerAction.RemoveHighlight: + if (currentStatus === 0) { + return 0; + } else if (currentStatus === 2 || currentStatus === 3) { + return 0.5; + } else { // 1 | -1 + return -1; + } + case MarkerAction.Select: + return 0.5; + case MarkerAction.Deselect: + if (currentStatus === 1 || currentStatus === 3) { + return 0.5; + } else if (currentStatus === 0) { + return 0; + } else { // 2 | -1 + return -1; + } + case MarkerAction.Toggle: + if (currentStatus === -1) { + return -1; + } else { // 0 | 1 | 2 | 3 + return 0.5; + } + case MarkerAction.Clear: + if (currentStatus === -1) { + return -1; + } else if (currentStatus === 0) { + return 0; + } else { // 1 | 2 | 3 + return 0.5; + } + case MarkerAction.None: + return -1; + default: + assertUnreachable(action); + } +} \ No newline at end of file