mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
599cfc1b1c | ||
|
|
84eccb5019 | ||
|
|
2dd07bb0e3 | ||
|
|
f404d23280 | ||
|
|
8b7333b470 | ||
|
|
7d26567d40 | ||
|
|
8f6dbf2192 | ||
|
|
db350ddfd3 | ||
|
|
c0144d826c | ||
|
|
de3e819b80 | ||
|
|
bbf96567b1 | ||
|
|
c9a3254bd6 | ||
|
|
bad6d030f1 | ||
|
|
e1ad67a059 | ||
|
|
8d3ac92989 | ||
|
|
d6eb334d12 | ||
|
|
c62f19623c | ||
|
|
77139afe7f | ||
|
|
54476ad85e | ||
|
|
6dd876232d | ||
|
|
ae2314d76c | ||
|
|
6667509745 | ||
|
|
5c871a5aae | ||
|
|
2482ef92af | ||
|
|
db59303a84 | ||
|
|
fe700953ff | ||
|
|
047946e41c | ||
|
|
f833efae37 | ||
|
|
be4b787e66 | ||
|
|
950b1c179a | ||
|
|
2bd1a01afb | ||
|
|
2fe43eda2b | ||
|
|
45fc0c61af | ||
|
|
7e7993f5ba | ||
|
|
9d34dbff0f | ||
|
|
ba1b03f01b | ||
|
|
791f7ca3c8 | ||
|
|
c14d50e4ff | ||
|
|
3e9de449c8 | ||
|
|
0132c7ef5e | ||
|
|
aa2222c086 | ||
|
|
f892917e1c | ||
|
|
f011025f16 | ||
|
|
9e44cd83fa | ||
|
|
e0e45b64ac | ||
|
|
f10b152252 | ||
|
|
b983df7eb5 |
28
CHANGELOG.md
28
CHANGELOG.md
@@ -4,8 +4,34 @@ All notable changes to this project will be documented in this file, following t
|
||||
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
|
||||
|
||||
|
||||
## [Unreleased]
|
||||
## [v2.3.0] - 2021-09-06
|
||||
|
||||
- Take include/exclude flags into account when displaying aromatic bonds
|
||||
- Improve marking performance
|
||||
- Avoid unnecessary draw calls/ui updates when marking
|
||||
- Check if loci is superset of visual
|
||||
- Check if loci overlaps with unit visual
|
||||
- Ensure ``Interval`` is used for ranges instead of ``SortedArray``
|
||||
- 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.3] - 2021-08-25
|
||||
|
||||
- Add ``invertCantorPairing`` helper function
|
||||
- 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``)
|
||||
|
||||
## [v2.2.2] - 2021-08-11
|
||||
|
||||
- Fix ``TransformData`` issues [#133](https://github.com/molstar/molstar/issues/133)
|
||||
- Fix ``mol-script`` query compiler const expression recognition.
|
||||
|
||||
## [v2.2.1] - 2021-08-02
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.2.1",
|
||||
"version": "2.3.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.2.1",
|
||||
"version": "2.3.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -39,7 +39,7 @@ interface ANVILContext {
|
||||
};
|
||||
|
||||
export const ANVILParams = {
|
||||
numberOfSpherePoints: PD.Numeric(140, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
|
||||
numberOfSpherePoints: PD.Numeric(175, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
|
||||
stepSize: PD.Numeric(1, { min: 0.25, max: 4, step: 0.25 }, { description: 'Thickness of membrane slices that will be tested' }),
|
||||
minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1}, { description: 'Minimum membrane thickness used during refinement' }),
|
||||
maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1}, { description: 'Maximum membrane thickness used during refinement' }),
|
||||
|
||||
@@ -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),
|
||||
@@ -228,7 +230,7 @@ interface Canvas3D {
|
||||
/** Sets drawPaused = false without starting the built in animation loop */
|
||||
resume(): void
|
||||
identify(x: number, y: number): PickData | undefined
|
||||
mark(loci: Representation.Loci, action: MarkerAction): void
|
||||
mark(loci: Representation.Loci, action: MarkerAction, noDraw?: boolean): void
|
||||
getLoci(pickingId: PickingId | undefined): Representation.Loci
|
||||
|
||||
notifyDidDraw: boolean,
|
||||
@@ -337,7 +339,7 @@ namespace Canvas3D {
|
||||
return { loci, repr };
|
||||
}
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction, noDraw = false) {
|
||||
const { repr, loci } = reprLoci;
|
||||
let changed = false;
|
||||
if (repr) {
|
||||
@@ -347,7 +349,7 @@ namespace Canvas3D {
|
||||
changed = helper.camera.mark(loci, action) || changed;
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
|
||||
}
|
||||
if (changed) {
|
||||
if (changed && !noDraw) {
|
||||
scene.update(void 0, true);
|
||||
helper.handle.scene.update(void 0, true);
|
||||
helper.camera.scene.update(void 0, true);
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
194
src/mol-canvas3d/passes/marking.ts
Normal file
194
src/mol-canvas3d/passes/marking.ts
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -16,7 +16,7 @@ export const start = Tuple.fst;
|
||||
export const end = Tuple.snd;
|
||||
export const min = Tuple.fst;
|
||||
export function max(i: Tuple) { return Tuple.snd(i) - 1; }
|
||||
export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); }
|
||||
export const size = Tuple.diff;
|
||||
export const hashCode = Tuple.hashCode;
|
||||
export const toString = Tuple.toString;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -36,6 +36,12 @@ namespace IntTuple {
|
||||
return _float64[0] as any;
|
||||
}
|
||||
|
||||
/** snd - fst */
|
||||
export function diff(t: IntTuple) {
|
||||
_float64[0] = t as any;
|
||||
return _int32[1] - _int32[0];
|
||||
}
|
||||
|
||||
export function fst(t: IntTuple): number {
|
||||
_float64[0] = t as any;
|
||||
return _int32[0];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-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>
|
||||
@@ -70,6 +70,15 @@ export function sortedCantorPairing(a: number, b: number) {
|
||||
return a < b ? cantorPairing(a, b) : cantorPairing(b, a);
|
||||
}
|
||||
|
||||
export function invertCantorPairing(out: [number, number], z: number) {
|
||||
const w = Math.floor((Math.sqrt(8 * z + 1) - 1) / 2);
|
||||
const t = (w * w + w) / 2;
|
||||
const y = z - t;
|
||||
out[0] = w - y;
|
||||
out[1] = y;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 32 bit FNV-1a hash, see http://isthe.com/chongo/tech/comp/fnv/
|
||||
*/
|
||||
|
||||
@@ -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,75 @@ 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>
|
||||
}
|
||||
|
||||
const MarkerCountLut = new Uint8Array(0x0303 + 1);
|
||||
MarkerCountLut[0x0001] = 1;
|
||||
MarkerCountLut[0x0002] = 1;
|
||||
MarkerCountLut[0x0003] = 1;
|
||||
MarkerCountLut[0x0100] = 1;
|
||||
MarkerCountLut[0x0200] = 1;
|
||||
MarkerCountLut[0x0300] = 1;
|
||||
MarkerCountLut[0x0101] = 2;
|
||||
MarkerCountLut[0x0201] = 2;
|
||||
MarkerCountLut[0x0301] = 2;
|
||||
MarkerCountLut[0x0102] = 2;
|
||||
MarkerCountLut[0x0202] = 2;
|
||||
MarkerCountLut[0x0302] = 2;
|
||||
MarkerCountLut[0x0103] = 2;
|
||||
MarkerCountLut[0x0203] = 2;
|
||||
MarkerCountLut[0x0303] = 2;
|
||||
|
||||
/**
|
||||
* Calculates the average number of entries that have any marker flag set.
|
||||
*
|
||||
* For alternative implementations and performance tests see
|
||||
* `src\perf-tests\markers-average.ts`.
|
||||
*/
|
||||
export function getMarkersAverage(array: Uint8Array, count: number): number {
|
||||
if (count === 0) return 0;
|
||||
|
||||
const view = new Uint32Array(array.buffer, 0, array.buffer.byteLength >> 2);
|
||||
const viewEnd = (count - 4) >> 2;
|
||||
const backStart = 4 * viewEnd;
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < viewEnd; ++i) {
|
||||
const v = view[i];
|
||||
sum += MarkerCountLut[v & 0xFFFF] + MarkerCountLut[v >> 16];
|
||||
}
|
||||
for (let i = backStart; 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 +85,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'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ const tmpCylinderCenter = Vec3();
|
||||
const tmpCylinderMat = Mat4();
|
||||
const tmpCylinderMatRot = Mat4();
|
||||
const tmpCylinderScale = Vec3();
|
||||
const tmpCylinderMatScale = Mat4();
|
||||
const tmpCylinderStart = Vec3();
|
||||
const tmpUp = Vec3();
|
||||
|
||||
@@ -32,9 +31,9 @@ function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number, matchDi
|
||||
// direction so the triangles of adjacent cylinder will line up
|
||||
if (matchDir) Vec3.matchDirection(tmpUp, up, tmpCylinderMatDir);
|
||||
else Vec3.copy(tmpUp, up);
|
||||
Mat4.fromScaling(tmpCylinderMatScale, Vec3.set(tmpCylinderScale, 1, length, 1));
|
||||
Vec3.set(tmpCylinderScale, 1, length, 1);
|
||||
Vec3.makeRotation(tmpCylinderMatRot, tmpUp, tmpCylinderMatDir);
|
||||
Mat4.mul(m, tmpCylinderMatRot, tmpCylinderMatScale);
|
||||
Mat4.scale(m, tmpCylinderMatRot, tmpCylinderScale);
|
||||
return Mat4.setTranslation(m, tmpCylinderCenter);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { Vec3, Mat4, Mat3, Vec4 } from '../../../mol-math/linear-algebra';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { transformPositionArray, transformDirectionArray, computeIndexedVertexNormals, GroupMapping, createGroupMapping} from '../../util';
|
||||
import { transformPositionArray, transformDirectionArray, computeIndexedVertexNormals, GroupMapping, createGroupMapping } from '../../util';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { TransformData } from '../transform-data';
|
||||
import { LocationIterator, PositionLocation } from '../../util/location-iterator';
|
||||
import { createColors } from '../color-data';
|
||||
import { ChunkedArray, hashFnv32a } from '../../../mol-data/util';
|
||||
import { ChunkedArray, hashFnv32a, invertCantorPairing, sortedCantorPairing } from '../../../mol-data/util';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
@@ -25,6 +25,8 @@ import { createEmptyOverpaint } from '../overpaint-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { createEmptyClipping } from '../clipping-data';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { arraySetAdd } from '../../../mol-util/array';
|
||||
import { degToRad } from '../../../mol-math/misc';
|
||||
|
||||
export interface Mesh {
|
||||
readonly kind: 'mesh',
|
||||
@@ -332,10 +334,10 @@ export namespace Mesh {
|
||||
mesh.vertexCount = newVertexCount;
|
||||
mesh.triangleCount = newTriangleCount;
|
||||
|
||||
ValueCell.update(vertexBuffer, newVb) as ValueCell<Float32Array>;
|
||||
ValueCell.update(groupBuffer, newGb) as ValueCell<Float32Array>;
|
||||
ValueCell.update(indexBuffer, newIb) as ValueCell<Uint32Array>;
|
||||
ValueCell.update(normalBuffer, newNb) as ValueCell<Float32Array>;
|
||||
ValueCell.update(vertexBuffer, newVb);
|
||||
ValueCell.update(groupBuffer, newGb);
|
||||
ValueCell.update(indexBuffer, newIb);
|
||||
ValueCell.update(normalBuffer, newNb);
|
||||
|
||||
// keep some original data, e.g., for geometry export
|
||||
(mesh.meta.originalData as OriginalData) = { indexBuffer: ib, vertexCount, triangleCount };
|
||||
@@ -345,6 +347,276 @@ export namespace Mesh {
|
||||
|
||||
//
|
||||
|
||||
function getNeighboursMap(mesh: Mesh) {
|
||||
const { vertexCount, triangleCount } = mesh;
|
||||
const elements = mesh.indexBuffer.ref.value;
|
||||
|
||||
const neighboursMap: number[][] = [];
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
neighboursMap[i] = [];
|
||||
}
|
||||
|
||||
for (let i = 0; i < triangleCount; ++i) {
|
||||
const v1 = elements[i * 3];
|
||||
const v2 = elements[i * 3 + 1];
|
||||
const v3 = elements[i * 3 + 2];
|
||||
arraySetAdd(neighboursMap[v1], v2);
|
||||
arraySetAdd(neighboursMap[v1], v3);
|
||||
arraySetAdd(neighboursMap[v2], v1);
|
||||
arraySetAdd(neighboursMap[v2], v3);
|
||||
arraySetAdd(neighboursMap[v3], v1);
|
||||
arraySetAdd(neighboursMap[v3], v2);
|
||||
}
|
||||
return neighboursMap;
|
||||
}
|
||||
|
||||
function getEdgeCounts(mesh: Mesh) {
|
||||
const { triangleCount } = mesh;
|
||||
const elements = mesh.indexBuffer.ref.value;
|
||||
|
||||
const edgeCounts = new Map<number, number>();
|
||||
const add = (a: number, b: number) => {
|
||||
const z = sortedCantorPairing(a, b);
|
||||
const c = edgeCounts.get(z) || 0;
|
||||
edgeCounts.set(z, c + 1);
|
||||
};
|
||||
|
||||
for (let i = 0; i < triangleCount; ++i) {
|
||||
const a = elements[i * 3];
|
||||
const b = elements[i * 3 + 1];
|
||||
const c = elements[i * 3 + 2];
|
||||
add(a, b); add(a, c); add(b, c);
|
||||
}
|
||||
return edgeCounts;
|
||||
}
|
||||
|
||||
function getBorderVertices(edgeCounts: Map<number, number>) {
|
||||
const borderVertices = new Set<number>();
|
||||
const pair: [number, number] = [0, 0];
|
||||
edgeCounts.forEach((c, z) => {
|
||||
if (c === 1) {
|
||||
invertCantorPairing(pair, z);
|
||||
borderVertices.add(pair[0]);
|
||||
borderVertices.add(pair[1]);
|
||||
}
|
||||
});
|
||||
|
||||
return borderVertices;
|
||||
}
|
||||
|
||||
function getBorderNeighboursMap(neighboursMap: number[][], borderVertices: Set<number>, edgeCounts: Map<number, number>) {
|
||||
const borderNeighboursMap = new Map<number, number[]>();
|
||||
const add = (v: number, nb: number) => {
|
||||
if (borderNeighboursMap.has(v)) arraySetAdd(borderNeighboursMap.get(v)!, nb);
|
||||
else borderNeighboursMap.set(v, [nb]);
|
||||
};
|
||||
|
||||
borderVertices.forEach(v => {
|
||||
const neighbours = neighboursMap[v];
|
||||
for (const nb of neighbours) {
|
||||
if (borderVertices.has(nb) && edgeCounts.get(sortedCantorPairing(v, nb)) === 1) {
|
||||
add(v, nb);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return borderNeighboursMap;
|
||||
}
|
||||
|
||||
function trimEdges(mesh: Mesh, neighboursMap: number[][]) {
|
||||
const { indexBuffer, triangleCount } = mesh;
|
||||
const ib = indexBuffer.ref.value;
|
||||
|
||||
// new
|
||||
const index = ChunkedArray.create(Uint32Array, 3, 1024, triangleCount);
|
||||
|
||||
let newTriangleCount = 0;
|
||||
for (let i = 0; i < triangleCount; ++i) {
|
||||
const a = ib[i * 3];
|
||||
const b = ib[i * 3 + 1];
|
||||
const c = ib[i * 3 + 2];
|
||||
if (neighboursMap[a].length === 2 ||
|
||||
neighboursMap[b].length === 2 ||
|
||||
neighboursMap[c].length === 2) continue;
|
||||
|
||||
ChunkedArray.add3(index, a, b, c);
|
||||
newTriangleCount += 1;
|
||||
}
|
||||
|
||||
const newIb = ChunkedArray.compact(index);
|
||||
mesh.triangleCount = newTriangleCount;
|
||||
ValueCell.update(indexBuffer, newIb);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
function fillEdges(mesh: Mesh, neighboursMap: number[][], borderNeighboursMap: Map<number, number[]>, maxLengthSquared: number) {
|
||||
const { vertexBuffer, indexBuffer, normalBuffer, triangleCount } = mesh;
|
||||
const vb = vertexBuffer.ref.value;
|
||||
const ib = indexBuffer.ref.value;
|
||||
const nb = normalBuffer.ref.value;
|
||||
|
||||
// new
|
||||
const index = ChunkedArray.create(Uint32Array, 3, 1024, triangleCount);
|
||||
|
||||
let newTriangleCount = 0;
|
||||
for (let i = 0; i < triangleCount; ++i) {
|
||||
ChunkedArray.add3(index, ib[i * 3], ib[i * 3 + 1], ib[i * 3 + 2]);
|
||||
newTriangleCount += 1;
|
||||
}
|
||||
|
||||
const vA = Vec3();
|
||||
const vB = Vec3();
|
||||
const vC = Vec3();
|
||||
const vD = Vec3();
|
||||
const vAB = Vec3();
|
||||
const vAC = Vec3();
|
||||
const vAD = Vec3();
|
||||
const vABC = Vec3();
|
||||
|
||||
const vAN = Vec3();
|
||||
const vN = Vec3();
|
||||
|
||||
const AngleThreshold = degToRad(120);
|
||||
const added = new Set<number>();
|
||||
|
||||
const indices = Array.from(borderNeighboursMap.keys())
|
||||
.filter(v => borderNeighboursMap.get(v)!.length < 2)
|
||||
.map(v => {
|
||||
const bnd = borderNeighboursMap.get(v)!;
|
||||
|
||||
Vec3.fromArray(vA, vb, v * 3);
|
||||
Vec3.fromArray(vB, vb, bnd[0] * 3);
|
||||
Vec3.fromArray(vC, vb, bnd[1] * 3);
|
||||
Vec3.sub(vAB, vB, vA);
|
||||
Vec3.sub(vAC, vC, vA);
|
||||
|
||||
return [v, Vec3.angle(vAB, vAC)];
|
||||
});
|
||||
|
||||
// start with the smallest angle
|
||||
indices.sort(([, a], [, b]) => a - b);
|
||||
|
||||
for (const [v, angle] of indices) {
|
||||
if (added.has(v) || angle > AngleThreshold) continue;
|
||||
|
||||
const nbs = borderNeighboursMap.get(v)!;
|
||||
if (neighboursMap[nbs[0]].includes(nbs[1]) &&
|
||||
!borderNeighboursMap.get(nbs[0])?.includes(nbs[1])
|
||||
) continue;
|
||||
|
||||
Vec3.fromArray(vA, vb, v * 3);
|
||||
Vec3.fromArray(vB, vb, nbs[0] * 3);
|
||||
Vec3.fromArray(vC, vb, nbs[1] * 3);
|
||||
Vec3.sub(vAB, vB, vA);
|
||||
Vec3.sub(vAC, vC, vA);
|
||||
Vec3.add(vABC, vAB, vAC);
|
||||
|
||||
if (Vec3.squaredDistance(vA, vB) >= maxLengthSquared) continue;
|
||||
|
||||
let add = false;
|
||||
for (const nb of neighboursMap[v]) {
|
||||
if (nbs.includes(nb)) continue;
|
||||
|
||||
Vec3.fromArray(vD, vb, nb * 3);
|
||||
Vec3.sub(vAD, vD, vA);
|
||||
if (Vec3.dot(vABC, vAD) < 0) {
|
||||
add = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!add) continue;
|
||||
|
||||
Vec3.fromArray(vAN, nb, v * 3);
|
||||
Vec3.triangleNormal(vN, vA, vB, vC);
|
||||
if (Vec3.dot(vN, vAN) > 0) {
|
||||
ChunkedArray.add3(index, v, nbs[0], nbs[1]);
|
||||
} else {
|
||||
ChunkedArray.add3(index, nbs[1], nbs[0], v);
|
||||
}
|
||||
added.add(v); added.add(nbs[0]); added.add(nbs[1]);
|
||||
newTriangleCount += 1;
|
||||
}
|
||||
|
||||
const newIb = ChunkedArray.compact(index);
|
||||
mesh.triangleCount = newTriangleCount;
|
||||
ValueCell.update(indexBuffer, newIb);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
function laplacianEdgeSmoothing(mesh: Mesh, borderNeighboursMap: Map<number, number[]>, options: { iterations: number, lambda: number }) {
|
||||
const { iterations, lambda } = options;
|
||||
|
||||
const a = Vec3();
|
||||
const b = Vec3();
|
||||
const c = Vec3();
|
||||
const t = Vec3();
|
||||
|
||||
const mu = -lambda;
|
||||
|
||||
let dst = new Float32Array(mesh.vertexBuffer.ref.value.length);
|
||||
|
||||
const step = (f: number) => {
|
||||
const pos = mesh.vertexBuffer.ref.value;
|
||||
dst.set(pos);
|
||||
|
||||
borderNeighboursMap.forEach((nbs, v) => {
|
||||
if (nbs.length !== 2) return;
|
||||
|
||||
Vec3.fromArray(a, pos, v * 3);
|
||||
Vec3.fromArray(b, pos, nbs[0] * 3);
|
||||
Vec3.fromArray(c, pos, nbs[1] * 3);
|
||||
|
||||
const wab = 1 / Vec3.distance(a, b);
|
||||
const wac = 1 / Vec3.distance(a, c);
|
||||
Vec3.scale(b, b, wab);
|
||||
Vec3.scale(c, c, wac);
|
||||
|
||||
Vec3.add(t, b, c);
|
||||
Vec3.scale(t, t, 1 / (wab + wac));
|
||||
Vec3.sub(t, t, a);
|
||||
|
||||
Vec3.scale(t, t, f);
|
||||
Vec3.add(t, a, t);
|
||||
|
||||
Vec3.toArray(t, dst, v * 3);
|
||||
});
|
||||
|
||||
const tmp = mesh.vertexBuffer.ref.value;
|
||||
ValueCell.update(mesh.vertexBuffer, dst);
|
||||
dst = tmp;
|
||||
};
|
||||
|
||||
for (let k = 0; k < iterations; ++k) {
|
||||
step(lambda);
|
||||
step(mu);
|
||||
}
|
||||
}
|
||||
|
||||
export function smoothEdges(mesh: Mesh, options: { iterations: number, maxNewEdgeLength: number }) {
|
||||
trimEdges(mesh, getNeighboursMap(mesh));
|
||||
|
||||
for (let k = 0; k < 10; ++k) {
|
||||
const oldTriangleCount = mesh.triangleCount;
|
||||
const edgeCounts = getEdgeCounts(mesh);
|
||||
const neighboursMap = getNeighboursMap(mesh);
|
||||
const borderVertices = getBorderVertices(edgeCounts);
|
||||
const borderNeighboursMap = getBorderNeighboursMap(neighboursMap, borderVertices, edgeCounts);
|
||||
fillEdges(mesh, neighboursMap, borderNeighboursMap, options.maxNewEdgeLength * options.maxNewEdgeLength);
|
||||
if (mesh.triangleCount === oldTriangleCount) break;
|
||||
}
|
||||
|
||||
const edgeCounts = getEdgeCounts(mesh);
|
||||
const neighboursMap = getNeighboursMap(mesh);
|
||||
const borderVertices = getBorderVertices(edgeCounts);
|
||||
const borderNeighboursMap = getBorderNeighboursMap(neighboursMap, borderVertices, edgeCounts);
|
||||
laplacianEdgeSmoothing(mesh, borderNeighboursMap, { iterations: options.iterations, lambda: 0.5 });
|
||||
return mesh;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -43,12 +43,13 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
|
||||
|
||||
if (transformData) {
|
||||
ValueCell.update(transformData.matrix, transformData.matrix.ref.value);
|
||||
ValueCell.update(transformData.transform, transformArray);
|
||||
const transform = transformData.transform.ref.value.length >= instanceCount * 16 ? transformData.transform.ref.value : new Float32Array(instanceCount * 16);
|
||||
transform.set(transformArray);
|
||||
ValueCell.update(transformData.transform, transform);
|
||||
ValueCell.updateIfChanged(transformData.uInstanceCount, instanceCount);
|
||||
ValueCell.updateIfChanged(transformData.instanceCount, instanceCount);
|
||||
|
||||
const aTransform = transformData.aTransform.ref.value.length >= instanceCount * 16 ? transformData.aTransform.ref.value : new Float32Array(instanceCount * 16);
|
||||
aTransform.set(transformArray);
|
||||
ValueCell.update(transformData.aTransform, aTransform);
|
||||
|
||||
// Note that this sets `extraTransform` to identity transforms
|
||||
@@ -59,14 +60,11 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
|
||||
ValueCell.update(transformData.aInstance, fillSerial(aInstance, instanceCount));
|
||||
|
||||
ValueCell.update(transformData.hasReflection, hasReflection);
|
||||
|
||||
updateTransformData(transformData);
|
||||
return transformData;
|
||||
} else {
|
||||
return {
|
||||
aTransform: ValueCell.create(new Float32Array(transformArray)),
|
||||
transformData = {
|
||||
aTransform: ValueCell.create(new Float32Array(instanceCount * 16)),
|
||||
matrix: ValueCell.create(Mat4.identity()),
|
||||
transform: ValueCell.create(transformArray),
|
||||
transform: ValueCell.create(new Float32Array(transformArray)),
|
||||
extraTransform: ValueCell.create(fillIdentityTransform(new Float32Array(instanceCount * 16), instanceCount)),
|
||||
uInstanceCount: ValueCell.create(instanceCount),
|
||||
instanceCount: ValueCell.create(instanceCount),
|
||||
@@ -74,6 +72,9 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
|
||||
hasReflection: ValueCell.create(hasReflection),
|
||||
};
|
||||
}
|
||||
|
||||
updateTransformData(transformData);
|
||||
return transformData;
|
||||
}
|
||||
|
||||
const identityTransform = new Float32Array(16);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -64,21 +64,19 @@ function createHistopyramidReductionRenderable(ctx: WebGLContext, inputLevel: Te
|
||||
}
|
||||
|
||||
type TextureFramebuffer = { texture: Texture, framebuffer: Framebuffer }
|
||||
const LevelTexturesFramebuffers: TextureFramebuffer[] = [];
|
||||
function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
|
||||
let textureFramebuffer = LevelTexturesFramebuffers[level];
|
||||
const size = Math.pow(2, level);
|
||||
if (textureFramebuffer === undefined) {
|
||||
const texture = ctx.isWebGL2
|
||||
? getTexture(`level${level}`, ctx, 'image-int32', 'alpha', 'int', 'nearest')
|
||||
: getTexture(`level${level}`, ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
texture.define(size, size);
|
||||
const framebuffer = getFramebuffer(`level${level}`, ctx);
|
||||
const name = `level${level}`;
|
||||
const texture = ctx.isWebGL2
|
||||
? getTexture(name, ctx, 'image-int32', 'alpha', 'int', 'nearest')
|
||||
: getTexture(name, ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
texture.define(size, size);
|
||||
let framebuffer = tryGetFramebuffer(name, ctx);
|
||||
if (!framebuffer) {
|
||||
framebuffer = getFramebuffer(name, ctx);
|
||||
texture.attachFramebuffer(framebuffer, 0);
|
||||
textureFramebuffer = { texture, framebuffer };
|
||||
LevelTexturesFramebuffers[level] = textureFramebuffer;
|
||||
}
|
||||
return textureFramebuffer;
|
||||
return { texture, framebuffer };
|
||||
}
|
||||
|
||||
function setRenderingDefaults(ctx: WebGLContext) {
|
||||
@@ -108,6 +106,11 @@ function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format
|
||||
return webgl.namedTextures[_name];
|
||||
}
|
||||
|
||||
function tryGetFramebuffer(name: string, webgl: WebGLContext): Framebuffer | undefined {
|
||||
const _name = `${HistogramPyramidName}-${name}`;
|
||||
return webgl.namedFramebuffers[_name];
|
||||
}
|
||||
|
||||
export interface HistogramPyramid {
|
||||
pyramidTex: Texture
|
||||
count: number
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { printTextureImage } from '../../mol-gl/renderable/util';
|
||||
import { PrintImageOptions, printTextureImage } from '../../mol-gl/renderable/util';
|
||||
import { defaults, ValueCell } from '../../mol-util';
|
||||
import { ValueSpec, AttributeSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
|
||||
import { Vec2 } from '../../mol-math/linear-algebra';
|
||||
import { GLRenderingContext } from '../../mol-gl/webgl/compat';
|
||||
import { PixelData } from '../../mol-util/image';
|
||||
|
||||
export const QuadPositions = new Float32Array([
|
||||
1.0, 1.0, -1.0, 1.0, -1.0, -1.0, // First triangle
|
||||
@@ -41,7 +42,7 @@ function getArrayForTexture(gl: GLRenderingContext, texture: Texture, size: numb
|
||||
throw new Error('unknown/unsupported texture type');
|
||||
}
|
||||
|
||||
export function readTexture(ctx: WebGLContext, texture: Texture, width?: number, height?: number) {
|
||||
export function readTexture(ctx: WebGLContext, texture: Texture, width?: number, height?: number): PixelData {
|
||||
const { gl, resources } = ctx;
|
||||
width = defaults(width, texture.getWidth());
|
||||
height = defaults(height, texture.getHeight());
|
||||
@@ -55,6 +56,8 @@ export function readTexture(ctx: WebGLContext, texture: Texture, width?: number,
|
||||
return { array, width, height };
|
||||
}
|
||||
|
||||
export function printTexture(ctx: WebGLContext, texture: Texture, scale: number) {
|
||||
printTextureImage(readTexture(ctx, texture), scale);
|
||||
export function printTexture(ctx: WebGLContext, texture: Texture, options: Partial<PrintImageOptions> = {}) {
|
||||
const pixelData = readTexture(ctx, texture);
|
||||
PixelData.flipY(pixelData);
|
||||
printTextureImage(pixelData, options);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -39,7 +39,15 @@ export function createTextureImage<T extends Uint8Array | Float32Array>(n: numbe
|
||||
return { array, width, height };
|
||||
}
|
||||
|
||||
export function printTextureImage(textureImage: TextureImage<any>, scale = 1) {
|
||||
const DefaultPrintImageOptions = {
|
||||
scale: 1,
|
||||
pixelated: false,
|
||||
id: 'molstar.debug.image'
|
||||
};
|
||||
export type PrintImageOptions = typeof DefaultPrintImageOptions
|
||||
|
||||
export function printTextureImage(textureImage: TextureImage<any>, options: Partial<PrintImageOptions> = {}) {
|
||||
|
||||
const { array, width, height } = textureImage;
|
||||
const itemSize = array.length / (width * height);
|
||||
const data = new Uint8ClampedArray(width * height * 4);
|
||||
@@ -54,32 +62,50 @@ export function printTextureImage(textureImage: TextureImage<any>, scale = 1) {
|
||||
} else {
|
||||
console.warn(`itemSize '${itemSize}' not supported`);
|
||||
}
|
||||
return printImageData(new ImageData(data, width, height), scale);
|
||||
return printImageData(new ImageData(data, width, height), options);
|
||||
}
|
||||
|
||||
export function printImageData(imageData: ImageData, scale = 1, pixelated = false) {
|
||||
const canvas = document.createElement('canvas');
|
||||
let tmpCanvas: HTMLCanvasElement;
|
||||
let tmpCanvasCtx: CanvasRenderingContext2D;
|
||||
let tmpContainer: HTMLDivElement;
|
||||
|
||||
export function printImageData(imageData: ImageData, options: Partial<PrintImageOptions> = {}) {
|
||||
const o = { ...DefaultPrintImageOptions, ...options };
|
||||
const canvas = tmpCanvas || document.createElement('canvas');
|
||||
tmpCanvas = canvas;
|
||||
canvas.width = imageData.width;
|
||||
canvas.height = imageData.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = tmpCanvasCtx || canvas.getContext('2d');
|
||||
tmpCanvasCtx = ctx;
|
||||
if (!ctx) throw new Error('Could not create canvas 2d context');
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
|
||||
if (!tmpContainer) {
|
||||
tmpContainer = document.createElement('div');
|
||||
tmpContainer.style.position = 'absolute';
|
||||
tmpContainer.style.bottom = '0px';
|
||||
tmpContainer.style.right = '0px';
|
||||
tmpContainer.style.border = 'solid orange';
|
||||
tmpContainer.style.pointerEvents = 'none';
|
||||
document.body.appendChild(tmpContainer);
|
||||
}
|
||||
|
||||
canvas.toBlob(imgBlob => {
|
||||
const objectURL = window.URL.createObjectURL(imgBlob);
|
||||
const img = document.createElement('img');
|
||||
const objectURL = URL.createObjectURL(imgBlob);
|
||||
const existingImg = document.getElementById(o.id) as HTMLImageElement;
|
||||
const img = existingImg || document.createElement('img');
|
||||
img.id = o.id;
|
||||
img.src = objectURL;
|
||||
img.style.width = imageData.width * scale + 'px';
|
||||
img.style.height = imageData.height * scale + 'px';
|
||||
if (pixelated) {
|
||||
img.style.width = imageData.width * o.scale + 'px';
|
||||
img.style.height = imageData.height * o.scale + 'px';
|
||||
if (o.pixelated) {
|
||||
// not supported in Firefox and IE
|
||||
img.style.imageRendering = 'pixelated';
|
||||
}
|
||||
img.style.position = 'relative';
|
||||
img.style.top = '0px';
|
||||
img.style.left = '0px';
|
||||
img.style.border = 'solid grey';
|
||||
img.style.pointerEvents = 'none';
|
||||
document.body.appendChild(img);
|
||||
if (!existingImg) tmpContainer.appendChild(img);
|
||||
}, 'image/png');
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -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
|
||||
`;
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
29
src/mol-gl/shader/marking/edge.frag.ts
Normal file
29
src/mol-gl/shader/marking/edge.frag.ts
Normal file
@@ -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);
|
||||
}
|
||||
`;
|
||||
23
src/mol-gl/shader/marking/overlay.frag.ts
Normal file
23
src/mol-gl/shader/marking/overlay.frag.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[];
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Loci {
|
||||
return !!loci && loci.kind === 'every-loci';
|
||||
}
|
||||
|
||||
export function isEmpty(loci: Loci): loci is EmptyLoci {
|
||||
export function isEmpty(loci: Loci) {
|
||||
if (isEveryLoci(loci)) return false;
|
||||
if (isEmptyLoci(loci)) return true;
|
||||
if (isDataLoci(loci)) return isDataLociEmpty(loci);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-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>
|
||||
@@ -22,9 +22,10 @@ import { NumberArray } from '../../../../mol-util/type-helpers';
|
||||
import { StructureProperties } from '../properties';
|
||||
import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
|
||||
import { Boundary } from '../../../../mol-math/geometry/boundary';
|
||||
import { IntTuple } from '../../../../mol-data/int/tuple';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const osSize = OrderedSet.size;
|
||||
const itDiff = IntTuple.diff;
|
||||
|
||||
/** Represents multiple structure element index locations */
|
||||
export interface Loci {
|
||||
@@ -65,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) {
|
||||
@@ -74,7 +78,15 @@ export namespace Loci {
|
||||
|
||||
export function size(loci: Loci) {
|
||||
let s = 0;
|
||||
for (const u of loci.elements) s += osSize(u.indices);
|
||||
// inlined for max performance, crucial for marking large cellpack models
|
||||
// `for (const u of loci.elements) s += OrderedSet.size(u.indices);`
|
||||
for (const { indices } of loci.elements) {
|
||||
if (typeof indices === 'number') {
|
||||
s += itDiff(indices as IntTuple);
|
||||
} else {
|
||||
s += (indices as SortedArray).length;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -131,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;
|
||||
|
||||
@@ -241,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>();
|
||||
@@ -285,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;
|
||||
@@ -307,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;
|
||||
@@ -458,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 {
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Sphere3D, SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { StructureUnitTransforms } from '../../mol-model/structure/structure/util/unit-transforms';
|
||||
|
||||
const _unwindMatrix = Mat4.zero();
|
||||
const _unwindMatrix = Mat4();
|
||||
export function unwindStructureAssembly(structure: Structure, unitTransforms: StructureUnitTransforms, t: number) {
|
||||
for (let i = 0, _i = structure.units.length; i < _i; i++) {
|
||||
const u = structure.units[i];
|
||||
@@ -20,15 +20,14 @@ export function unwindStructureAssembly(structure: Structure, unitTransforms: St
|
||||
}
|
||||
}
|
||||
|
||||
const _centerVec = Vec3.zero(), _transVec = Vec3.zero(), _transMat = Mat4.zero();
|
||||
export function explodeStructure(structure: Structure, unitTransforms: StructureUnitTransforms, t: number) {
|
||||
const boundary = structure.boundary.sphere;
|
||||
const d = boundary.radius * t;
|
||||
const _centerVec = Vec3(), _transVec = Vec3(), _transMat = Mat4();
|
||||
export function explodeStructure(structure: Structure, unitTransforms: StructureUnitTransforms, t: number, sphere: Sphere3D) {
|
||||
const d = sphere.radius * t;
|
||||
|
||||
for (let i = 0, _i = structure.units.length; i < _i; i++) {
|
||||
const u = structure.units[i];
|
||||
Vec3.transformMat4(_centerVec, u.lookup3d.boundary.sphere.center, u.conformation.operator.matrix);
|
||||
Vec3.sub(_transVec, _centerVec, boundary.center);
|
||||
Vec3.sub(_transVec, _centerVec, sphere.center);
|
||||
Vec3.setMagnitude(_transVec, _transVec, d);
|
||||
Mat4.fromTranslation(_transMat, _transVec);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateBuilder, StateObjectCell, StateSelection, StateTransform } from '../../mol-state';
|
||||
import { StructureComponentRef } from '../manager/structure/hierarchy-state';
|
||||
import { EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { EmptyLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { Clipping } from '../../mol-theme/clipping';
|
||||
|
||||
type ClippingEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, clipping?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.ClippingStructureRepresentation3DFromBundle>>) => Promise<void>
|
||||
@@ -25,7 +25,7 @@ export async function setStructureClipping(plugin: PluginContext, components: St
|
||||
// always use the root structure to get the loci so the clipping
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
if (Loci.isEmpty(loci)) return;
|
||||
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) return;
|
||||
|
||||
const layer = {
|
||||
bundle: StructureElement.Bundle.fromLoci(loci),
|
||||
|
||||
@@ -13,7 +13,7 @@ import { StateBuilder, StateObjectCell, StateSelection, StateTransform } from '.
|
||||
import { Overpaint } from '../../mol-theme/overpaint';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { StructureComponentRef } from '../manager/structure/hierarchy-state';
|
||||
import { EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { EmptyLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
|
||||
|
||||
type OverpaintEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, overpaint?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle>>) => Promise<void>
|
||||
const OverpaintManagerTag = 'overpaint-controls';
|
||||
@@ -26,7 +26,7 @@ export async function setStructureOverpaint(plugin: PluginContext, components: S
|
||||
// always use the root structure to get the loci so the overpaint
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
if (Loci.isEmpty(loci)) return;
|
||||
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) return;
|
||||
|
||||
const layer = {
|
||||
bundle: StructureElement.Bundle.fromLoci(loci),
|
||||
|
||||
@@ -11,7 +11,7 @@ import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateBuilder, StateObjectCell, StateSelection, StateTransform } from '../../mol-state';
|
||||
import { StructureComponentRef } from '../manager/structure/hierarchy-state';
|
||||
import { EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { EmptyLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { Transparency } from '../../mol-theme/transparency';
|
||||
|
||||
type TransparencyEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, transparency?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.TransparencyStructureRepresentation3DFromBundle>>) => Promise<void>
|
||||
@@ -25,7 +25,7 @@ export async function setStructureTransparency(plugin: PluginContext, components
|
||||
// always use the root structure to get the loci so the transparency
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
if (Loci.isEmpty(loci)) return;
|
||||
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) return;
|
||||
|
||||
const layer = {
|
||||
bundle: StructureElement.Bundle.fromLoci(loci),
|
||||
|
||||
@@ -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>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -74,7 +74,13 @@ namespace InteractivityManager {
|
||||
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
|
||||
|
||||
export type LociMarkProvider = (loci: Representation.Loci, action: MarkerAction) => void
|
||||
/**
|
||||
* The `noRender` argument indicates that the action should only update the internal
|
||||
* data structure but not render anything user visible. For example 1) no drawing of
|
||||
* the canvas3d scene or 2) no ui update of loci labels. This is useful because some
|
||||
* actions require clearing any markings before they can be applied.
|
||||
*/
|
||||
export type LociMarkProvider = (loci: Representation.Loci, action: MarkerAction, /* test */ noRender?: boolean) => void
|
||||
|
||||
export abstract class LociMarkManager {
|
||||
protected providers: LociMarkProvider[] = [];
|
||||
@@ -101,9 +107,9 @@ namespace InteractivityManager {
|
||||
return { loci: Loci.normalize(loci, granularity), repr };
|
||||
}
|
||||
|
||||
protected mark(current: Representation.Loci, action: MarkerAction) {
|
||||
protected mark(current: Representation.Loci, action: MarkerAction, noRender = false) {
|
||||
if (!Loci.isEmpty(current.loci)) {
|
||||
for (let p of this.providers) p(current, action);
|
||||
for (let p of this.providers) p(current, action, noRender);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,9 +136,9 @@ namespace InteractivityManager {
|
||||
this.prev.push(loci);
|
||||
}
|
||||
|
||||
clearHighlights = () => {
|
||||
clearHighlights = (noRender = false) => {
|
||||
for (const p of this.prev) {
|
||||
this.mark(p, MarkerAction.RemoveHighlight);
|
||||
this.mark(p, MarkerAction.RemoveHighlight, noRender);
|
||||
}
|
||||
this.prev.length = 0;
|
||||
}
|
||||
@@ -147,21 +153,29 @@ namespace InteractivityManager {
|
||||
highlightOnly(current: Representation.Loci, applyGranularity = true) {
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
if (!this.isHighlighted(normalized)) {
|
||||
this.clearHighlights();
|
||||
this.addHighlight(normalized);
|
||||
if (Loci.isEmpty(normalized.loci)) {
|
||||
this.clearHighlights();
|
||||
} else {
|
||||
this.clearHighlights(true);
|
||||
this.addHighlight(normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlightOnlyExtend(current: Representation.Loci, applyGranularity = true) {
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
if (StructureElement.Loci.is(normalized.loci)) {
|
||||
const loci = {
|
||||
const extended = {
|
||||
loci: this.sel.tryGetRange(normalized.loci) || normalized.loci,
|
||||
repr: normalized.repr
|
||||
};
|
||||
if (!this.isHighlighted(loci)) {
|
||||
this.clearHighlights();
|
||||
this.addHighlight(loci);
|
||||
if (!this.isHighlighted(extended)) {
|
||||
if (Loci.isEmpty(extended.loci)) {
|
||||
this.clearHighlights();
|
||||
} else {
|
||||
this.clearHighlights(true);
|
||||
this.addHighlight(extended);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,7 +255,7 @@ namespace InteractivityManager {
|
||||
// do a full deselect/select for the current structure so visuals that are
|
||||
// marked with granularity unequal to 'element' and join/intersect operations
|
||||
// are handled properly
|
||||
super.mark({ loci: Structure.Loci(loci.structure) }, MarkerAction.Deselect);
|
||||
super.mark({ loci: Structure.Loci(loci.structure) }, MarkerAction.Deselect, true);
|
||||
super.mark({ loci: this.sel.getLoci(loci.structure) }, MarkerAction.Select);
|
||||
} else {
|
||||
super.mark(current, action);
|
||||
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -92,9 +92,9 @@ export class LociLabelManager {
|
||||
}
|
||||
|
||||
constructor(public ctx: PluginContext) {
|
||||
ctx.managers.interactivity.lociHighlights.addProvider((loci, action) => {
|
||||
ctx.managers.interactivity.lociHighlights.addProvider((loci, action, noRender) => {
|
||||
this.mark(loci, action);
|
||||
this.showLabels();
|
||||
if (!noRender) this.showLabels();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -236,23 +236,23 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.sourceData;
|
||||
const unitTransforms = new StructureUnitTransforms(structure.root);
|
||||
explodeStructure(structure, unitTransforms, params.t);
|
||||
const unitTransforms = new StructureUnitTransforms(structure);
|
||||
explodeStructure(structure, unitTransforms, params.t, structure.root.boundary.sphere);
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { unitTransforms },
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure.root) },
|
||||
info: structure.root,
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure) },
|
||||
info: structure,
|
||||
repr: a.data.repr
|
||||
}, { label: `Explode T = ${params.t.toFixed(2)}` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = a.data.sourceData;
|
||||
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
|
||||
if (b.data.info !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
explodeStructure(structure.root, unitTransforms, newParams.t);
|
||||
explodeStructure(structure, unitTransforms, newParams.t, structure.root.boundary.sphere);
|
||||
b.label = `Explode T = ${newParams.t.toFixed(2)}`;
|
||||
b.data.repr = a.data.repr;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
@@ -275,27 +275,27 @@ const SpinStructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.sourceData;
|
||||
const unitTransforms = new StructureUnitTransforms(structure.root);
|
||||
const unitTransforms = new StructureUnitTransforms(structure);
|
||||
|
||||
const { axis, origin } = getSpinStructureAxisAndOrigin(structure.root, params);
|
||||
spinStructure(structure, unitTransforms, params.t, axis, origin);
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { unitTransforms },
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure.root) },
|
||||
info: structure.root,
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure) },
|
||||
info: structure,
|
||||
repr: a.data.repr
|
||||
}, { label: `Spin T = ${params.t.toFixed(2)}` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = a.data.sourceData;
|
||||
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
|
||||
if (b.data.info !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t && oldParams.axis === newParams.axis && oldParams.origin === newParams.origin) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
const { axis, origin } = getSpinStructureAxisAndOrigin(structure.root, newParams);
|
||||
spinStructure(structure.root, unitTransforms, newParams.t, axis, origin);
|
||||
spinStructure(structure, unitTransforms, newParams.t, axis, origin);
|
||||
b.label = `Spin T = ${newParams.t.toFixed(2)}`;
|
||||
b.data.repr = a.data.repr;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
|
||||
@@ -143,7 +143,7 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
|
||||
historyEntry(e: StructureSelectionHistoryEntry, idx: number) {
|
||||
const history = this.plugin.managers.structure.selection.additionsHistory;
|
||||
return <div className='msp-flex-row' key={e.id}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={this.plugin.managers.interactivity.lociHighlights.clearHighlights}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={() => this.plugin.managers.interactivity.lociHighlights.clearHighlights()}>
|
||||
{idx}. <span dangerouslySetInnerHTML={{ __html: e.label }} />
|
||||
</Button>
|
||||
{history.length > 1 && <IconButton svg={ArrowUpwardSvg} small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'up')} flex='20px' title={'Move up'} />}
|
||||
|
||||
@@ -210,7 +210,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
|
||||
|
||||
lociEntry(e: LociEntry, idx: number) {
|
||||
return <div className='msp-flex-row' key={idx}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={this.plugin.managers.interactivity.lociHighlights.clearHighlights}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={() => this.plugin.managers.interactivity.lociHighlights.clearHighlights()}>
|
||||
<span dangerouslySetInnerHTML={{ __html: e.label }} />
|
||||
</Button>
|
||||
</div>;
|
||||
@@ -219,7 +219,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
|
||||
historyEntry(e: StructureSelectionHistoryEntry, idx: number) {
|
||||
const history = this.plugin.managers.structure.selection.additionsHistory;
|
||||
return <div className='msp-flex-row' key={e.id}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={this.plugin.managers.interactivity.lociHighlights.clearHighlights}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={() => this.plugin.managers.interactivity.lociHighlights.clearHighlights()}>
|
||||
{idx}. <span dangerouslySetInnerHTML={{ __html: e.label }} />
|
||||
</Button>
|
||||
{history.length > 1 && <IconButton svg={ArrowUpwardSvg} small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'up')} flex='20px' title={'Move up'} />}
|
||||
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -42,9 +42,9 @@ export const HighlightLoci = PluginBehavior.create({
|
||||
name: 'representation-highlight-loci',
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<HighlightLociProps> {
|
||||
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => {
|
||||
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction, noRender?: boolean) => {
|
||||
if (!this.ctx.canvas3d || !this.params.mark) return;
|
||||
this.ctx.canvas3d.mark(interactionLoci, action);
|
||||
this.ctx.canvas3d.mark(interactionLoci, action, noRender);
|
||||
}
|
||||
register() {
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, buttons, modifiers }) => {
|
||||
@@ -104,9 +104,9 @@ export const SelectLoci = PluginBehavior.create({
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<SelectLociProps> {
|
||||
private spine: StateTreeSpine.Impl
|
||||
private lociMarkProvider = (reprLoci: Representation.Loci, action: MarkerAction) => {
|
||||
private lociMarkProvider = (reprLoci: Representation.Loci, action: MarkerAction, noRender?: boolean) => {
|
||||
if (!this.ctx.canvas3d || !this.params.mark) return;
|
||||
this.ctx.canvas3d.mark({ loci: reprLoci.loci }, action);
|
||||
this.ctx.canvas3d.mark({ loci: reprLoci.loci }, action, noRender);
|
||||
}
|
||||
private applySelectMark(ref: string, clear?: boolean) {
|
||||
const cell = this.ctx.state.data.cells.get(ref);
|
||||
|
||||
@@ -305,7 +305,7 @@ export namespace VolumeStreaming {
|
||||
|
||||
private _invTransform: Mat4 = Mat4();
|
||||
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
|
||||
if (Loci.isEmpty(loci)) {
|
||||
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) {
|
||||
return Box3D();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,7 +14,7 @@ import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-obj
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { Task } from '../../mol-task';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { EmptyLoci, Loci, isEveryLoci, isDataLoci } from '../../mol-model/loci';
|
||||
import { EmptyLoci, Loci, isEveryLoci, isDataLoci, EveryLoci } from '../../mol-model/loci';
|
||||
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
|
||||
import { Overpaint } from '../../mol-theme/overpaint';
|
||||
import { StructureParams } from './params';
|
||||
@@ -77,6 +77,10 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
|
||||
if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
|
||||
// Remap `loci` from equivalent structure to the current `_structure`
|
||||
loci = Loci.remap(loci, _structure);
|
||||
if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
|
||||
// Change to `EveryLoci` to allow for downstream optimizations
|
||||
loci = EveryLoci;
|
||||
}
|
||||
} else if (!isEveryLoci(loci) && !isDataLoci(loci)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-obj
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { Task } from '../../mol-task';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci } from '../../mol-model/loci';
|
||||
import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci, EveryLoci } from '../../mol-model/loci';
|
||||
import { MarkerAction, MarkerActions, applyMarkerAction } from '../../mol-util/marker-action';
|
||||
import { Overpaint } from '../../mol-theme/overpaint';
|
||||
import { Transparency } from '../../mol-theme/transparency';
|
||||
@@ -196,6 +196,10 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
|
||||
// Remap `loci` from equivalent structure to the current `_structure`
|
||||
loci = Loci.remap(loci, _structure);
|
||||
if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
|
||||
// Change to `EveryLoci` to allow for downstream optimizations
|
||||
loci = EveryLoci;
|
||||
}
|
||||
} else if (!isEveryLoci(loci) && !isDataLoci(loci)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -133,7 +133,7 @@ function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme
|
||||
const o = edges[edgeIndex].props.order;
|
||||
const f = BitFlags.create(edges[edgeIndex].props.flag);
|
||||
if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
|
||||
// show metall coordinations and hydrogen bonds with dashed cylinders
|
||||
// show metallic coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
|
||||
@@ -71,7 +71,7 @@ function createInterUnitBondLines(ctx: VisualContext, structure: Structure, them
|
||||
const o = edges[edgeIndex].props.order;
|
||||
const f = BitFlags.create(edges[edgeIndex].props.flag);
|
||||
if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
|
||||
// show metall coordinations and hydrogen bonds with dashed cylinders
|
||||
// show metallic coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkBuilderProps,
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, UnitsCylindersParams, UnitsCylindersVisual } from '../units-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BondType } from '../../../mol-model/structure/model/types';
|
||||
import { BondCylinderParams, BondIterator, eachIntraBond, getIntraBondLoci, makeIntraBondIgnoreTest } from './util/bond';
|
||||
import { BondCylinderParams, BondIterator, eachIntraBond, getIntraBondLoci, ignoreBondType, makeIntraBondIgnoreTest } from './util/bond';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { IntAdjacencyGraph } from '../../../mol-math/graph';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
@@ -33,7 +33,11 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
const bonds = unit.bonds;
|
||||
const { edgeCount, a, b, edgeProps, offset } = bonds;
|
||||
const { order: _order, flags: _flags } = edgeProps;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds } = props;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds, includeTypes, excludeTypes } = props;
|
||||
|
||||
const include = BondType.fromNames(includeTypes);
|
||||
const exclude = BondType.fromNames(excludeTypes);
|
||||
const ignoreComputedAromatic = ignoreBondType(include, exclude, BondType.Flag.Computed);
|
||||
|
||||
const vRef = Vec3(), delta = Vec3();
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
@@ -123,7 +127,7 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
const o = _order[edgeIndex];
|
||||
const f = _flags[edgeIndex];
|
||||
if (isBondType(f, BondType.Flag.MetallicCoordination) || isBondType(f, BondType.Flag.HydrogenBond)) {
|
||||
// show metall coordinations and hydrogen bonds with dashed cylinders
|
||||
// show metallic coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
@@ -133,7 +137,7 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
const bR = elementAromaticRingIndices.get(bI);
|
||||
const arCount = (aR && bR) ? arrayIntersectionSize(aR, bR) : 0;
|
||||
|
||||
if (arCount || isBondType(f, BondType.Flag.Aromatic)) {
|
||||
if (isBondType(f, BondType.Flag.Aromatic) || (arCount && !ignoreComputedAromatic)) {
|
||||
if (arCount === 2) {
|
||||
return LinkStyle.MirroredAromatic;
|
||||
} else {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { LinkStyle, createLinkLines, LinkBuilderProps } from './util/link';
|
||||
import { UnitsVisual, UnitsLinesParams, UnitsLinesVisual } from '../units-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BondType } from '../../../mol-model/structure/model/types';
|
||||
import { BondIterator, BondLineParams, getIntraBondLoci, eachIntraBond, makeIntraBondIgnoreTest } from './util/bond';
|
||||
import { BondIterator, BondLineParams, getIntraBondLoci, eachIntraBond, makeIntraBondIgnoreTest, ignoreBondType } from './util/bond';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Lines } from '../../../mol-geo/geometry/lines/lines';
|
||||
import { IntAdjacencyGraph } from '../../../mol-math/graph';
|
||||
@@ -36,11 +36,15 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
const elements = unit.elements;
|
||||
const bonds = unit.bonds;
|
||||
const { edgeCount, a, b, edgeProps, offset } = bonds;
|
||||
const { order: _order, flags: _flags } = edgeProps;
|
||||
const { sizeFactor, aromaticBonds } = props;
|
||||
|
||||
if (!edgeCount) return Lines.createEmpty(lines);
|
||||
|
||||
const { order: _order, flags: _flags } = edgeProps;
|
||||
const { sizeFactor, aromaticBonds, includeTypes, excludeTypes } = props;
|
||||
|
||||
const include = BondType.fromNames(includeTypes);
|
||||
const exclude = BondType.fromNames(excludeTypes);
|
||||
const ignoreComputedAromatic = ignoreBondType(include, exclude, BondType.Flag.Computed);
|
||||
|
||||
const vRef = Vec3();
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
|
||||
@@ -84,7 +88,7 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
const o = _order[edgeIndex];
|
||||
const f = _flags[edgeIndex];
|
||||
if (isBondType(f, BondType.Flag.MetallicCoordination) || isBondType(f, BondType.Flag.HydrogenBond)) {
|
||||
// show metall coordinations and hydrogen bonds with dashed cylinders
|
||||
// show metallic coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
@@ -94,7 +98,7 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
const bR = elementAromaticRingIndices.get(bI);
|
||||
const arCount = (aR && bR) ? arrayIntersectionSize(aR, bR) : 0;
|
||||
|
||||
if (arCount || isBondType(f, BondType.Flag.Aromatic)) {
|
||||
if (isBondType(f, BondType.Flag.Aromatic) || (arCount && !ignoreComputedAromatic)) {
|
||||
if (arCount === 2) {
|
||||
return LinkStyle.MirroredAromatic;
|
||||
} else {
|
||||
|
||||
@@ -44,6 +44,7 @@ export const GaussianDensityVolumeParams = {
|
||||
...ComplexDirectVolumeParams,
|
||||
...GaussianDensityParams,
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
includeParent: PD.Boolean(false, { isHidden: true }),
|
||||
};
|
||||
export type GaussianDensityVolumeParams = typeof GaussianDensityVolumeParams
|
||||
|
||||
@@ -99,6 +100,7 @@ export const UnitsGaussianDensityVolumeParams = {
|
||||
...UnitsDirectVolumeParams,
|
||||
...GaussianDensityParams,
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
includeParent: PD.Boolean(false, { isHidden: true }),
|
||||
};
|
||||
export type UnitsGaussianDensityVolumeParams = typeof UnitsGaussianDensityVolumeParams
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ const SharedParams = {
|
||||
...ColorSmoothingParams,
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
tryUseGpu: PD.Boolean(true),
|
||||
includeParent: PD.Boolean(false, { isHidden: true }),
|
||||
};
|
||||
type SharedParams = typeof SharedParams
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ export const GaussianWireframeParams = {
|
||||
sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }),
|
||||
lineSizeAttenuation: PD.Boolean(false),
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
includeParent: PD.Boolean(false, { isHidden: true }),
|
||||
};
|
||||
export type GaussianWireframeParams = typeof GaussianWireframeParams
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { VisualContext } from '../../visual';
|
||||
import { Unit, Structure } from '../../../mol-model/structure';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { computeUnitMolecularSurface, MolecularSurfaceProps } from './util/molecular-surface';
|
||||
import { computeUnitMolecularSurface } from './util/molecular-surface';
|
||||
import { computeMarchingCubesMesh } from '../../../mol-geo/util/marching-cubes/algorithm';
|
||||
import { ElementIterator, getElementLoci, eachElement } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
@@ -29,6 +29,7 @@ export const MolecularSurfaceMeshParams = {
|
||||
...ColorSmoothingParams,
|
||||
};
|
||||
export type MolecularSurfaceMeshParams = typeof MolecularSurfaceMeshParams
|
||||
export type MolecularSurfaceMeshProps = PD.Values<MolecularSurfaceMeshParams>
|
||||
|
||||
type MolecularSurfaceMeta = {
|
||||
resolution?: number
|
||||
@@ -37,7 +38,7 @@ type MolecularSurfaceMeta = {
|
||||
|
||||
//
|
||||
|
||||
async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, mesh?: Mesh): Promise<Mesh> {
|
||||
async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceMeshProps, mesh?: Mesh): Promise<Mesh> {
|
||||
const { transform, field, idField, resolution } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime);
|
||||
|
||||
const params = {
|
||||
@@ -47,6 +48,11 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
|
||||
};
|
||||
const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime);
|
||||
|
||||
if (props.includeParent) {
|
||||
const iterations = Math.ceil(2 / props.resolution);
|
||||
Mesh.smoothEdges(surface, { iterations, maxNewEdgeLength: Math.sqrt(2) });
|
||||
}
|
||||
|
||||
Mesh.transform(surface, transform);
|
||||
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
|
||||
|
||||
@@ -71,6 +77,7 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
|
||||
if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
|
||||
if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
|
||||
if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
|
||||
|
||||
if (newProps.smoothColors.name !== currentProps.smoothColors.name) {
|
||||
state.updateColor = true;
|
||||
} else if (newProps.smoothColors.name === 'on' && currentProps.smoothColors.name === 'on') {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class SymbolRuntimeImpl<S extends MSymbol> implements QuerySymbolRuntime {
|
||||
constArgs = true;
|
||||
} else if (Expression.isArgumentsArray(inputArgs)) {
|
||||
args = [];
|
||||
constArgs = false;
|
||||
constArgs = true;
|
||||
for (const arg of inputArgs) {
|
||||
const compiled = _compile(ctx, arg);
|
||||
constArgs = constArgs && compiled.isConst;
|
||||
@@ -128,7 +128,7 @@ class SymbolRuntimeImpl<S extends MSymbol> implements QuerySymbolRuntime {
|
||||
}
|
||||
} else {
|
||||
args = Object.create(null);
|
||||
constArgs = false;
|
||||
constArgs = true;
|
||||
for (const key of Object.keys(inputArgs)) {
|
||||
const compiled = _compile(ctx, inputArgs[key]);
|
||||
constArgs = constArgs && compiled.isConst;
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
export { PixelData };
|
||||
|
||||
interface PixelData {
|
||||
readonly array: Uint8Array
|
||||
readonly array: Uint8Array | Float32Array
|
||||
readonly width: number
|
||||
readonly height: number
|
||||
}
|
||||
|
||||
namespace PixelData {
|
||||
export function create(array: Uint8Array, width: number, height: number): PixelData {
|
||||
export function create(array: Uint8Array | Float32Array, width: number, height: number): PixelData {
|
||||
return { array, width, height };
|
||||
}
|
||||
|
||||
@@ -36,8 +36,9 @@ namespace PixelData {
|
||||
/** to undo pre-multiplied alpha */
|
||||
export function divideByAlpha(pixelData: PixelData): PixelData {
|
||||
const { array } = pixelData;
|
||||
const factor = (array instanceof Uint8Array) ? 255 : 1;
|
||||
for (let i = 0, il = array.length; i < il; i += 4) {
|
||||
const a = array[i + 3] / 255;
|
||||
const a = array[i + 3] / factor;
|
||||
array[i] /= a;
|
||||
array[i + 1] /= a;
|
||||
array[i + 2] /= a;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
207
src/perf-tests/markers-average.ts
Normal file
207
src/perf-tests/markers-average.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import * as B from 'benchmark';
|
||||
|
||||
function uint8ForLoop(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;
|
||||
}
|
||||
|
||||
function uint32ForLoop(array: Uint8Array, count: number): number {
|
||||
if (count === 0) return 0;
|
||||
|
||||
const view = new Uint32Array(array.buffer, 0, array.buffer.byteLength >> 2);
|
||||
const viewEnd = (count - 4) >> 2;
|
||||
const backStart = 4 * viewEnd;
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < viewEnd; ++i) {
|
||||
const v = view[i];
|
||||
sum += ((v & 0xFF) && 1) + ((v & 0xFF00) && 1) + ((v & 0xFF0000) && 1) + ((v & 0xFF000000) && 1);
|
||||
}
|
||||
for (let i = backStart; i < count; ++i) {
|
||||
sum += array[i] && 1;
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
function uint32ForLoopAddOnlyBaseline(array: Uint8Array, count: number): number {
|
||||
if (count === 0) return 0;
|
||||
|
||||
const view = new Uint32Array(array.buffer, 0, array.buffer.byteLength >> 2);
|
||||
const viewEnd = (count - 4) >> 2;
|
||||
const backStart = 4 * viewEnd;
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < viewEnd; ++i) {
|
||||
const v = view[i];
|
||||
sum += v;
|
||||
}
|
||||
for (let i = backStart; i < count; ++i) {
|
||||
sum += array[i] && 1;
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
function createTypedLut() {
|
||||
const lut = new Uint8Array(0x0303 + 1);
|
||||
lut[0x0001] = 1;
|
||||
lut[0x0002] = 1;
|
||||
lut[0x0003] = 1;
|
||||
lut[0x0100] = 1;
|
||||
lut[0x0200] = 1;
|
||||
lut[0x0300] = 1;
|
||||
lut[0x0101] = 2;
|
||||
lut[0x0201] = 2;
|
||||
lut[0x0301] = 2;
|
||||
lut[0x0102] = 2;
|
||||
lut[0x0202] = 2;
|
||||
lut[0x0302] = 2;
|
||||
lut[0x0103] = 2;
|
||||
lut[0x0203] = 2;
|
||||
lut[0x0303] = 2;
|
||||
return lut;
|
||||
}
|
||||
|
||||
function createNativeLut() {
|
||||
const lut: number[] = [];
|
||||
for (let i = 0, il = 0x0303 + 1; i < il; ++i) lut[i] = 0;
|
||||
lut[0x0000] = 0;
|
||||
lut[0x0001] = 1;
|
||||
lut[0x0002] = 1;
|
||||
lut[0x0003] = 1;
|
||||
lut[0x0100] = 1;
|
||||
lut[0x0200] = 1;
|
||||
lut[0x0300] = 1;
|
||||
lut[0x0101] = 2;
|
||||
lut[0x0201] = 2;
|
||||
lut[0x0301] = 2;
|
||||
lut[0x0102] = 2;
|
||||
lut[0x0202] = 2;
|
||||
lut[0x0302] = 2;
|
||||
lut[0x0103] = 2;
|
||||
lut[0x0203] = 2;
|
||||
lut[0x0303] = 2;
|
||||
return lut;
|
||||
}
|
||||
|
||||
function createTypedLut2bits() {
|
||||
const lut = new Uint8Array(256);
|
||||
for (let a = 0; a < 4; ++a) {
|
||||
for (let b = 0; b < 4; ++b) {
|
||||
for (let c = 0; c < 4; ++c) {
|
||||
for (let d = 0; d < 4; ++d) {
|
||||
const i = d | c << 2 | b << 4 | a << 6;
|
||||
const v = (a && 1) + (b && 1) + (c && 1) + (d && 1);
|
||||
lut[i] = v;
|
||||
// console.log([a, b, c, d], i, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lut;
|
||||
}
|
||||
|
||||
const lutNative = createNativeLut();
|
||||
const lutTyped = createTypedLut();
|
||||
const lutTyped2bits = createTypedLut2bits();
|
||||
|
||||
function uint32ForLoopWithLutNative(array: Uint8Array, count: number): number {
|
||||
if (count === 0) return 0;
|
||||
|
||||
const view = new Uint32Array(array.buffer, 0, array.buffer.byteLength >> 2);
|
||||
const viewEnd = (count - 4) >> 2;
|
||||
const backStart = 4 * viewEnd;
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < viewEnd; ++i) {
|
||||
const v = view[i];
|
||||
sum += lutNative[v & 0xFFFF] + lutNative[v >> 16];
|
||||
}
|
||||
for (let i = backStart; i < count; ++i) {
|
||||
sum += array[i] && 1;
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
function uint32ForLoopWithLutTyped(array: Uint8Array, count: number): number {
|
||||
if (count === 0) return 0;
|
||||
|
||||
const view = new Uint32Array(array.buffer, 0, array.buffer.byteLength >> 2);
|
||||
const viewEnd = (count - 4) >> 2;
|
||||
const backStart = 4 * viewEnd;
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < viewEnd; ++i) {
|
||||
const v = view[i];
|
||||
sum += lutTyped[v & 0xFFFF] + lutTyped[v >> 16];
|
||||
}
|
||||
for (let i = backStart; i < count; ++i) {
|
||||
sum += array[i] && 1;
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
function uint32ForLoopWithLut2bits(array: Uint8Array, count: number): number {
|
||||
if (count === 0) return 0;
|
||||
|
||||
const view = new Uint32Array(array.buffer, 0, array.buffer.byteLength >> 2);
|
||||
const viewEnd = (count - 4) >> 2;
|
||||
const backStart = 4 * viewEnd;
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < viewEnd; ++i) {
|
||||
const v = view[i];
|
||||
sum += lutTyped2bits[(v >> 18 | v >> 12 | v >> 6 | v) & 0xFF];
|
||||
}
|
||||
for (let i = backStart; i < count; ++i) {
|
||||
sum += array[i] && 1;
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
function createData(elements: number, instances: number) {
|
||||
const data = new Uint8Array(4000 * 5000);
|
||||
const start = Math.floor(instances / 2);
|
||||
data.fill(1, start, start + elements);
|
||||
return data;
|
||||
};
|
||||
|
||||
export function run(elements: number, instances: number) {
|
||||
const suite = new B.Suite();
|
||||
const count = elements * instances;
|
||||
const data = createData(elements, instances);
|
||||
|
||||
console.log('uint8ForLoop', uint8ForLoop(data, count));
|
||||
console.log('uint32ForLoop', uint32ForLoop(data, count));
|
||||
console.log('uint32ForLoopWithLutNative', uint32ForLoopWithLutNative(data, count));
|
||||
console.log('uint32ForLoopWithLutTyped', uint32ForLoopWithLutTyped(data, count));
|
||||
console.log('uint32ForLoopWithLut2bits', uint32ForLoopWithLut2bits(data, count));
|
||||
|
||||
suite
|
||||
.add('uint8ForLoop', () => uint8ForLoop(data, count))
|
||||
.add('uint32ForLoop', () => uint32ForLoop(data, count))
|
||||
.add('uint32ForLoopAddOnlyBaseline', () => uint32ForLoopAddOnlyBaseline(data, count))
|
||||
.add('uint32ForLoopWithLutNative', () => uint32ForLoopWithLutNative(data, count))
|
||||
.add('uint32ForLoopWithLutTyped', () => uint32ForLoopWithLutTyped(data, count))
|
||||
.add('uint32ForLoopWithLut2bits', () => uint32ForLoopWithLut2bits(data, count))
|
||||
.on('cycle', (e: any) => console.log(String(e.target)))
|
||||
.run();
|
||||
}
|
||||
|
||||
// console.log(createTypedLut2bits());
|
||||
|
||||
// run(5000, 4000);
|
||||
// uint8ForLoop 0.00025
|
||||
// uint32ForLoop 0.00025
|
||||
// uint32ForLoopWithLutNative 0.00025
|
||||
// uint32ForLoopWithLutTyped 0.00025
|
||||
// uint32ForLoopWithLut2bits 0.00025
|
||||
// uint8ForLoop x 49.84 ops/sec ±3.30% (64 runs sampled)
|
||||
// uint32ForLoop x 97.70 ops/sec ±1.71% (71 runs sampled)
|
||||
// uint32ForLoopAddOnlyBaseline x 220 ops/sec ±2.49% (85 runs sampled)
|
||||
// uint32ForLoopWithLutNative x 135 ops/sec ±1.71% (76 runs sampled)
|
||||
// uint32ForLoopWithLutTyped x 137 ops/sec ±1.69% (77 runs sampled)
|
||||
// uint32ForLoopWithLut2bits x 111 ops/sec ±2.73% (70 runs sampled)
|
||||
@@ -33,7 +33,7 @@ function test() {
|
||||
fontAtlas.get(String.fromCharCode(0x212B));
|
||||
console.timeEnd('Angstrom Sign');
|
||||
|
||||
printTextureImage(fontAtlas.texture, 0.5);
|
||||
printTextureImage(fontAtlas.texture, { scale: 0.5 });
|
||||
console.log(`${Object.keys(fontAtlas.mapped).length} chars prepared`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user