diff --git a/package.json b/package.json index 958ae82e5e71b18103f1473b4195d3239c1e10b2..db9112321dcdf8a7a153c1fd06db86cf3285120e 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,8 @@ "contributors": [ "Alexander Rose <alexander.rose@weirdbyte.de>", "David Sehnal <david.sehnal@gmail.com>", - "Sebastian Bittrich <sebastian.bittrich@rcsb.org>" + "Sebastian Bittrich <sebastian.bittrich@rcsb.org>", + "Ludovic Autin <autin@scripps.edu>" ], "license": "MIT", "devDependencies": { diff --git a/src/mol-geo/geometry/clipping-data.ts b/src/mol-geo/geometry/clipping-data.ts new file mode 100644 index 0000000000000000000000000000000000000000..5c2209feda0c08ab2ce743fb86872f39bbc44a68 --- /dev/null +++ b/src/mol-geo/geometry/clipping-data.ts @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ValueCell } from '../../mol-util/value-cell'; +import { Vec2 } from '../../mol-math/linear-algebra'; +import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; +import { Clipping } from '../../mol-theme/clipping'; + +export type ClippingData = { + dClipObjectCount: ValueCell<number>, + dClipVariant: ValueCell<string>, + + tClipping: ValueCell<TextureImage<Uint8Array>> + uClippingTexDim: ValueCell<Vec2> + dClipping: ValueCell<boolean>, +} + +export function applyClippingGroups(array: Uint8Array, start: number, end: number, groups: Clipping.Groups) { + for (let i = start; i < end; ++i) { + array[i] = groups; + } + return true; +} + +export function clearClipping(array: Uint8Array, start: number, end: number) { + array.fill(0, start, end); +} + +export function createClipping(count: number, clippingData?: ClippingData): ClippingData { + const clipping = createTextureImage(Math.max(1, count), 1, Uint8Array, clippingData && clippingData.tClipping.ref.value.array); + if (clippingData) { + ValueCell.update(clippingData.tClipping, clipping); + ValueCell.update(clippingData.uClippingTexDim, Vec2.create(clipping.width, clipping.height)); + ValueCell.update(clippingData.dClipping, count > 0); + return clippingData; + } else { + return { + dClipObjectCount: ValueCell.create(0), + dClipVariant: ValueCell.create('instance'), + + tClipping: ValueCell.create(clipping), + uClippingTexDim: ValueCell.create(Vec2.create(clipping.width, clipping.height)), + dClipping: ValueCell.create(count > 0), + }; + } +} + +const emptyClippingTexture = { array: new Uint8Array(1), width: 1, height: 1 }; +export function createEmptyClipping(clippingData?: ClippingData): ClippingData { + if (clippingData) { + ValueCell.update(clippingData.tClipping, emptyClippingTexture); + ValueCell.update(clippingData.uClippingTexDim, Vec2.create(1, 1)); + return clippingData; + } else { + return { + dClipObjectCount: ValueCell.create(0), + dClipVariant: ValueCell.create('instance'), + + tClipping: ValueCell.create(emptyClippingTexture), + uClippingTexDim: ValueCell.create(Vec2.create(1, 1)), + dClipping: ValueCell.create(false), + }; + } +} \ No newline at end of file diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index 75257f19da6f1ec541391560e2b04e8b29f3c467..f1088713cf6067a48b06bb5f2d7e444120f47e14 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/src/mol-geo/geometry/direct-volume/direct-volume.ts @@ -12,7 +12,7 @@ import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume'; import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util'; import { Texture } from '../../../mol-gl/webgl/texture'; import { Box3D, Sphere3D } from '../../../mol-math/geometry'; -import { Mat4, Vec2, Vec3 } from '../../../mol-math/linear-algebra'; +import { Mat4, Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra'; import { Theme } from '../../../mol-theme/theme'; import { ValueCell } from '../../../mol-util'; import { Color } from '../../../mol-util/color'; @@ -26,6 +26,7 @@ import { createEmptyOverpaint } from '../overpaint-data'; import { TransformData } from '../transform-data'; import { createEmptyTransparency } from '../transparency-data'; import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function'; +import { createEmptyClipping } from '../clipping-data'; const VolumeBox = Box(); const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]; @@ -140,6 +141,7 @@ export namespace DirectVolume { const marker = createMarkers(instanceCount * groupCount); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); const counts = { drawCount: VolumeBox.indices.length, groupCount, instanceCount }; @@ -156,6 +158,7 @@ export namespace DirectVolume { ...marker, ...overpaint, ...transparency, + ...clipping, ...transform, ...BaseGeometry.createValues(props, counts), @@ -163,6 +166,7 @@ export namespace DirectVolume { elements: ValueCell.create(VolumeBox.indices as Uint32Array), boundingSphere: ValueCell.create(boundingSphere), invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)), uIsoValue: ValueCell.create(props.isoValueNorm), uBboxMin: bboxMin, @@ -204,6 +208,7 @@ export namespace DirectVolume { } if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere); + ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere)); } } diff --git a/src/mol-geo/geometry/image/image.ts b/src/mol-geo/geometry/image/image.ts index 6f559cb4f402df6844ffe83f6e976d6c648e9beb..486ffc593022aa88b20415437381831c5df5e13b 100644 --- a/src/mol-geo/geometry/image/image.ts +++ b/src/mol-geo/geometry/image/image.ts @@ -9,7 +9,7 @@ import { LocationIterator } from '../../../mol-geo/util/location-iterator'; import { RenderableState } from '../../../mol-gl/renderable'; import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere, TextureImage } from '../../../mol-gl/renderable/util'; import { Sphere3D } from '../../../mol-math/geometry'; -import { Vec2 } from '../../../mol-math/linear-algebra'; +import { Vec2, Vec4 } from '../../../mol-math/linear-algebra'; import { Theme } from '../../../mol-theme/theme'; import { ValueCell } from '../../../mol-util'; import { Color } from '../../../mol-util/color'; @@ -23,6 +23,7 @@ import { TransformData } from '../transform-data'; import { createEmptyTransparency } from '../transparency-data'; import { ImageValues } from '../../../mol-gl/renderable/image'; import { fillSerial } from '../../../mol-util/array'; +import { createEmptyClipping } from '../clipping-data'; const QuadIndices = new Uint32Array([ 0, 1, 2, @@ -137,6 +138,7 @@ namespace Image { const marker = createMarkers(instanceCount * groupCount); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); const counts = { drawCount: QuadIndices.length, groupCount, instanceCount }; @@ -148,6 +150,7 @@ namespace Image { ...marker, ...overpaint, ...transparency, + ...clipping, ...transform, ...BaseGeometry.createValues(props, counts), @@ -159,6 +162,7 @@ namespace Image { aGroup: ValueCell.create(fillSerial(new Float32Array(4))), boundingSphere: ValueCell.create(boundingSphere), invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)), dInterpolation: ValueCell.create(props.interpolation), @@ -188,6 +192,7 @@ namespace Image { } if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere); + ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere)); } } diff --git a/src/mol-geo/geometry/lines/lines.ts b/src/mol-geo/geometry/lines/lines.ts index 80f32670f32a025fd40ad39851b6d93825e087b9..e80116f83ca00791df16be34330be1bd89c87d65 100644 --- a/src/mol-geo/geometry/lines/lines.ts +++ b/src/mol-geo/geometry/lines/lines.ts @@ -5,7 +5,7 @@ */ import { ValueCell } from '../../../mol-util'; -import { Mat4 } from '../../../mol-math/linear-algebra'; +import { Mat4, Vec4 } from '../../../mol-math/linear-algebra'; import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util'; import { GeometryUtils } from '../geometry'; import { createColors } from '../color-data'; @@ -25,6 +25,7 @@ import { BaseGeometry } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { hashFnv32a } from '../../../mol-data/util'; +import { createEmptyClipping } from '../clipping-data'; /** Wide line */ export interface Lines { @@ -184,6 +185,7 @@ export namespace Lines { const marker = createMarkers(instanceCount * groupCount); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); const counts = { drawCount: lines.lineCount * 2 * 3, groupCount, instanceCount }; @@ -198,11 +200,13 @@ export namespace Lines { elements: lines.indexBuffer, boundingSphere: ValueCell.create(boundingSphere), invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)), ...color, ...size, ...marker, ...overpaint, ...transparency, + ...clipping, ...transform, ...BaseGeometry.createValues(props, counts), @@ -234,6 +238,7 @@ export namespace Lines { } if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere); + ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere)); } } } \ No newline at end of file diff --git a/src/mol-geo/geometry/mesh/mesh.ts b/src/mol-geo/geometry/mesh/mesh.ts index dc20ed0e2b31f90f49ff8e71923d7962c1a7d86b..48c9c20bdc22e523204befa2872a67cbae29081e 100644 --- a/src/mol-geo/geometry/mesh/mesh.ts +++ b/src/mol-geo/geometry/mesh/mesh.ts @@ -6,7 +6,7 @@ */ import { ValueCell } from '../../../mol-util'; -import { Vec3, Mat4, Mat3 } from '../../../mol-math/linear-algebra'; +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 { GeometryUtils } from '../geometry'; @@ -23,6 +23,7 @@ import { Color } from '../../../mol-util/color'; import { BaseGeometry } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; +import { createEmptyClipping } from '../clipping-data'; export interface Mesh { readonly kind: 'mesh', @@ -355,6 +356,7 @@ export namespace Mesh { const marker = createMarkers(instanceCount * groupCount); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); const counts = { drawCount: mesh.triangleCount * 3, groupCount, instanceCount }; @@ -368,10 +370,12 @@ export namespace Mesh { elements: mesh.indexBuffer, boundingSphere: ValueCell.create(boundingSphere), invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)), ...color, ...marker, ...overpaint, ...transparency, + ...clipping, ...transform, ...BaseGeometry.createValues(props, counts), @@ -405,6 +409,7 @@ export namespace Mesh { } if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere); + ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere)); } } } diff --git a/src/mol-geo/geometry/points/points.ts b/src/mol-geo/geometry/points/points.ts index 3ae70ef5d15a07e29216a03240d7deae3e72f1a0..8c2f99e5444dd8e21511e3d0b900c9f51bd25822 100644 --- a/src/mol-geo/geometry/points/points.ts +++ b/src/mol-geo/geometry/points/points.ts @@ -5,7 +5,7 @@ */ import { ValueCell } from '../../../mol-util'; -import { Mat4 } from '../../../mol-math/linear-algebra'; +import { Mat4, Vec4 } from '../../../mol-math/linear-algebra'; import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util'; import { GeometryUtils } from '../geometry'; import { createColors } from '../color-data'; @@ -24,6 +24,7 @@ import { BaseGeometry } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { hashFnv32a } from '../../../mol-data/util'; +import { createEmptyClipping } from '../clipping-data'; /** Point cloud */ export interface Points { @@ -143,6 +144,7 @@ export namespace Points { const marker = createMarkers(instanceCount * groupCount); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); const counts = { drawCount: points.pointCount, groupCount, instanceCount }; @@ -154,11 +156,13 @@ export namespace Points { aGroup: points.groupBuffer, boundingSphere: ValueCell.create(boundingSphere), invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)), ...color, ...size, ...marker, ...overpaint, ...transparency, + ...clipping, ...transform, ...BaseGeometry.createValues(props, counts), @@ -192,6 +196,7 @@ export namespace Points { } if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere); + ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere)); } } diff --git a/src/mol-geo/geometry/spheres/spheres.ts b/src/mol-geo/geometry/spheres/spheres.ts index 85266d2756547da653b13b9a2704ae624ef30c5c..97a730cd73278bb39faa13bf23533583e6eb48f0 100644 --- a/src/mol-geo/geometry/spheres/spheres.ts +++ b/src/mol-geo/geometry/spheres/spheres.ts @@ -22,6 +22,8 @@ import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { hashFnv32a } from '../../../mol-data/util'; import { GroupMapping, createGroupMapping } from '../../util'; +import { createEmptyClipping } from '../clipping-data'; +import { Vec4 } from '../../../mol-math/linear-algebra'; export interface Spheres { readonly kind: 'spheres', @@ -147,6 +149,7 @@ export namespace Spheres { const marker = createMarkers(instanceCount * groupCount); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); const counts = { drawCount: spheres.sphereCount * 2 * 3, groupCount, instanceCount }; @@ -161,11 +164,13 @@ export namespace Spheres { elements: spheres.indexBuffer, boundingSphere: ValueCell.create(boundingSphere), invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)), ...color, ...size, ...marker, ...overpaint, ...transparency, + ...clipping, ...transform, padding: ValueCell.create(padding), @@ -200,6 +205,7 @@ export namespace Spheres { } if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere); + ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere)); } ValueCell.update(values.padding, padding); } diff --git a/src/mol-geo/geometry/text/text.ts b/src/mol-geo/geometry/text/text.ts index 08195c2c0a24b5a7dd655091d75a3d9d3b1b2ef0..7d28f5fb8baae763bb02510b1779b29c708a99d3 100644 --- a/src/mol-geo/geometry/text/text.ts +++ b/src/mol-geo/geometry/text/text.ts @@ -18,7 +18,7 @@ import { Sphere3D } from '../../../mol-math/geometry'; import { TextureImage, createTextureImage, calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util'; import { TextValues } from '../../../mol-gl/renderable/text'; import { Color } from '../../../mol-util/color'; -import { Vec3 } from '../../../mol-math/linear-algebra'; +import { Vec3, Vec4 } from '../../../mol-math/linear-algebra'; import { FontAtlasParams } from './font-atlas'; import { RenderableState } from '../../../mol-gl/renderable'; import { clamp } from '../../../mol-math/interpolate'; @@ -28,6 +28,7 @@ import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { hashFnv32a } from '../../../mol-data/util'; import { GroupMapping, createGroupMapping } from '../../util'; +import { createEmptyClipping } from '../clipping-data'; type TextAttachment = ( 'bottom-left' | 'bottom-center' | 'bottom-right' | @@ -195,6 +196,7 @@ export namespace Text { const marker = createMarkers(instanceCount * groupCount); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); const counts = { drawCount: text.charCount * 2 * 3, groupCount, instanceCount }; @@ -210,11 +212,13 @@ export namespace Text { elements: text.indexBuffer, boundingSphere: ValueCell.create(boundingSphere), invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)), ...color, ...size, ...marker, ...overpaint, ...transparency, + ...clipping, ...transform, aTexCoord: text.tcoordBuffer, @@ -269,6 +273,7 @@ export namespace Text { } if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere); + ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere)); } ValueCell.update(values.padding, padding); } diff --git a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts index 255bd4ff69ab3a29875da2547284dc1c45c074fb..cdc2d0f1f71c15c63f0c25ceb46b8f2f562054f2 100644 --- a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts +++ b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -20,8 +20,9 @@ import { createEmptyTransparency } from '../transparency-data'; import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh'; import { calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util'; import { Texture } from '../../../mol-gl/webgl/texture'; -import { Vec2 } from '../../../mol-math/linear-algebra'; +import { Vec2, Vec4 } from '../../../mol-math/linear-algebra'; import { fillSerial } from '../../../mol-util/array'; +import { createEmptyClipping } from '../clipping-data'; export interface TextureMesh { readonly kind: 'texture-mesh', @@ -93,6 +94,7 @@ export namespace TextureMesh { const marker = createMarkers(instanceCount * groupCount); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); const counts = { drawCount: textureMesh.vertexCount, groupCount, instanceCount }; @@ -107,11 +109,13 @@ export namespace TextureMesh { aGroup: ValueCell.create(fillSerial(new Float32Array(textureMesh.vertexCount))), boundingSphere: ValueCell.create(transformBoundingSphere), invariantBoundingSphere: ValueCell.create(Sphere3D.clone(textureMesh.boundingSphere)), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(textureMesh.boundingSphere)), ...color, ...marker, ...overpaint, ...transparency, + ...clipping, ...transform, ...BaseGeometry.createValues(props, counts), @@ -149,6 +153,7 @@ export namespace TextureMesh { } if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere); + ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere)); } } } \ No newline at end of file diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index 716364b2d3e1e58a5cd48ddf984b906db8e4940b..9d6ed9a61030826b552183501fa1abbfa4000037 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -7,7 +7,7 @@ import { createGl } from './gl.shim'; import { Camera } from '../../mol-canvas3d/camera'; -import { Vec3, Mat4 } from '../../mol-math/linear-algebra'; +import { Vec3, Mat4, Vec4 } from '../../mol-math/linear-algebra'; import { ValueCell } from '../../mol-util'; import Renderer from '../renderer'; @@ -24,6 +24,7 @@ import { Color } from '../../mol-util/color'; import { Sphere3D } from '../../mol-math/geometry'; import { createEmptyOverpaint } from '../../mol-geo/geometry/overpaint-data'; import { createEmptyTransparency } from '../../mol-geo/geometry/transparency-data'; +import { createEmptyClipping } from '../../mol-geo/geometry/clipping-data'; function createRenderer(gl: WebGLRenderingContext) { const ctx = createContext(gl); @@ -43,6 +44,7 @@ function createPoints() { const marker = createEmptyMarkers(); const overpaint = createEmptyOverpaint(); const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); const aTransform = ValueCell.create(new Float32Array(16)); const m4 = Mat4.identity(); @@ -63,10 +65,12 @@ function createPoints() { ...size, ...overpaint, ...transparency, + ...clipping, uAlpha: ValueCell.create(1.0), uInstanceCount: ValueCell.create(1), uGroupCount: ValueCell.create(3), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere.ref.value)), alpha: ValueCell.create(1.0), drawCount: ValueCell.create(3), diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 785ce2b9fdb27280ad520a039552a5fa5e9cd580..a952165c52a1c401c442d730adf119449bbb1d39 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -29,8 +29,15 @@ export const DirectVolumeSchema = { dTransparency: DefineSpec('boolean'), dTransparencyVariant: DefineSpec('string', ['single', 'multi']), + dClipObjectCount: DefineSpec('number'), + dClipVariant: DefineSpec('string', ['instance', 'pixel']), + uClippingTexDim: UniformSpec('v2'), + tClipping: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), + dClipping: DefineSpec('boolean'), + uInstanceCount: UniformSpec('i'), uGroupCount: UniformSpec('i'), + uInvariantBoundingSphere: UniformSpec('v4'), aInstance: AttributeSpec('float32', 1, 1), aTransform: AttributeSpec('float32', 16, 1), diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index b2131e7b6e48406774e1fe09b9112b696d314ecf..303cbe60f888903d1b2f5417a0fa173324b922c5 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -1,22 +1,21 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { ValueCell } from '../../mol-util'; -import { AttributeItemSize, ElementsKind, AttributeValues, AttributeKind } from '../webgl/buffer'; -import { UniformKind, UniformValues } from '../webgl/uniform'; +import { AttributeItemSize, ElementsKind, AttributeValues, AttributeKind, DataTypeArrayType } from '../webgl/buffer'; +import { UniformKind, UniformValues, UniformKindValue } from '../webgl/uniform'; import { DefineKind, DefineValues } from '../shader-code'; -import { Vec2, Vec3, Vec4, Mat3, Mat4 } from '../../mol-math/linear-algebra'; -import { TextureImage, TextureVolume } from './util'; -import { TextureValues, TextureType, TextureFormat, TextureFilter, TextureKind, Texture } from '../webgl/texture'; +import { Mat4 } from '../../mol-math/linear-algebra'; +import { TextureValues, TextureType, TextureFormat, TextureFilter, TextureKind, TextureKindValue } from '../webgl/texture'; import { Sphere3D } from '../../mol-math/geometry'; export type ValueKindType = { 'number': number 'string': string - 'boolean': string + 'boolean': boolean 'any': any 'm4': Mat4, @@ -27,40 +26,7 @@ export type ValueKind = keyof ValueKindType // -export type KindValue = { - 'f': number - 'i': number - 'v2': Vec2 - 'v3': Vec3 - 'v4': Vec4 - 'm3': Mat3 - 'm4': Mat4 - 't': number - - 'uint8': Uint8Array - 'int8': Int8Array - 'uint16': Uint16Array - 'int16': Int16Array - 'uint32': Uint32Array - 'int32': Int32Array - 'float32': Float32Array - - 'image-uint8': TextureImage<Uint8Array> - 'image-float32': TextureImage<Float32Array> - 'image-depth': TextureImage<Uint8Array> // TODO should be Uint32Array - 'volume-uint8': TextureVolume<Uint8Array> - 'volume-float32': TextureVolume<Float32Array> - 'texture': Texture - 'texture2d': Texture - 'texture3d': Texture - - 'number': number - 'string': string - 'boolean': boolean - 'any': any - - 'sphere': Sphere3D -} +export type KindValue = UniformKindValue & DataTypeArrayType & TextureKindValue & ValueKindType export type Values<S extends RenderableSchema> = { readonly [k in keyof S]: ValueCell<KindValue[S[k]['kind']]> } @@ -163,6 +129,11 @@ export const GlobalUniformSchema = { uTransparentBackground: UniformSpec('i'), + uClipObjectType: UniformSpec('i[]'), + uClipObjectPosition: UniformSpec('v3[]'), + uClipObjectRotation: UniformSpec('v4[]'), + uClipObjectScale: UniformSpec('v3[]'), + // all the following could in principle be per object // as a kind of 'material' parameter set // would need to test performance implications @@ -228,21 +199,31 @@ export type OverpaintSchema = typeof OverpaintSchema export type OverpaintValues = Values<OverpaintSchema> export const TransparencySchema = { - // aTransparency: AttributeSpec('float32', 1, 0), // TODO uTransparencyTexDim: UniformSpec('v2'), tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dTransparency: DefineSpec('boolean'), - // dTransparencyType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'group_instance']), // TODO dTransparencyVariant: DefineSpec('string', ['single', 'multi']), } as const; export type TransparencySchema = typeof TransparencySchema export type TransparencyValues = Values<TransparencySchema> +export const ClippingSchema = { + dClipObjectCount: DefineSpec('number'), + dClipVariant: DefineSpec('string', ['instance', 'pixel']), + + uClippingTexDim: UniformSpec('v2'), + tClipping: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), + dClipping: DefineSpec('boolean'), +} as const; +export type ClippingSchema = typeof ClippingSchema +export type ClippingValues = Values<ClippingSchema> + export const BaseSchema = { ...ColorSchema, ...MarkerSchema, ...OverpaintSchema, ...TransparencySchema, + ...ClippingSchema, aInstance: AttributeSpec('float32', 1, 1), aGroup: AttributeSpec('float32', 1, 0), @@ -258,6 +239,7 @@ export const BaseSchema = { uAlpha: UniformSpec('f', true), uInstanceCount: UniformSpec('i'), uGroupCount: UniformSpec('i'), + uInvariantBoundingSphere: UniformSpec('v4'), drawCount: ValueSpec('number'), instanceCount: ValueSpec('number'), @@ -272,7 +254,7 @@ export const BaseSchema = { /** additional per-instance transform, see aTransform */ extraTransform: ValueSpec('float32'), - /** bounding sphere taking aTransform into account */ + /** bounding sphere taking aTransform into account and encompases all instances */ boundingSphere: ValueSpec('sphere'), /** bounding sphere NOT taking aTransform into account */ invariantBoundingSphere: ValueSpec('sphere'), diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 4dde07d4df56d10bb12cc78f4647be4c6ca47763..48f9d2b98c82559ba3c378ffd5900e714d349662 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -9,13 +9,15 @@ import { Camera } from '../mol-canvas3d/camera'; import Scene from './scene'; import { WebGLContext } from './webgl/context'; -import { Mat4, Vec3, Vec4, Vec2 } from '../mol-math/linear-algebra'; +import { Mat4, Vec3, Vec4, Vec2, Quat } from '../mol-math/linear-algebra'; import { Renderable } from './renderable'; import { Color } from '../mol-util/color'; -import { ValueCell } from '../mol-util'; +import { ValueCell, deepEqual } from '../mol-util'; import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema'; import { GraphicsRenderVariant } from './webgl/render-item'; import { ParamDefinition as PD } from '../mol-util/param-definition'; +import { Clipping } from '../mol-theme/clipping'; +import { stringToWords } from '../mol-util/string'; export interface RendererStats { programCount: number @@ -71,6 +73,19 @@ export const RendererParams = { metallic: PD.Group({}), plastic: PD.Group({}), }, { label: 'Lighting', description: 'Style in which the 3D scene is rendered/lighted' }), + + clip: PD.Group({ + variant: PD.Select('instance', PD.arrayToOptions<Clipping.Variant>(['instance', 'pixel'])), + objects: PD.ObjectList({ + type: PD.Select('plane', PD.objectToOptions(Clipping.Type, t => stringToWords(t))), + position: PD.Vec3(Vec3()), + rotation: PD.Group({ + axis: PD.Vec3(Vec3.create(1, 0, 0)), + angle: PD.Numeric(0, { min: -180, max: 180, step: 0.1 }), + }, { isExpanded: true }), + scale: PD.Vec3(Vec3.create(1, 1, 1)), + }, o => stringToWords(o.type)) + }) }; export type RendererProps = PD.Values<typeof RendererParams> @@ -106,11 +121,44 @@ function getStyle(props: RendererProps['style']) { } } +type Clip = { + variant: Clipping.Variant + objects: { + count: number + type: number[] + position: number[] + rotation: number[] + scale: number[] + } +} + +const tmpQuat = Quat(); +function getClip(props: RendererProps['clip'], clip?: Clip): Clip { + const { type, position, rotation, scale } = clip?.objects || { + type: (new Array(5)).fill(1), + position: (new Array(5 * 3)).fill(0), + rotation: (new Array(5 * 4)).fill(0), + scale: (new Array(5 * 3)).fill(1), + }; + for (let i = 0, il = props.objects.length; i < il; ++i) { + const p = props.objects[i]; + type[i] = Clipping.Type[p.type]; + Vec3.toArray(p.position, position, i * 3); + Quat.toArray(Quat.setAxisAngle(tmpQuat, p.rotation.axis, p.rotation.angle), rotation, i * 4); + Vec3.toArray(p.scale, scale, i * 3); + } + return { + variant: props.variant, + objects: { count: props.objects.length, type, position, rotation, scale } + }; +} + namespace Renderer { export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer { const { gl, state, stats } = ctx; const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props); const style = getStyle(p.style); + const clip = getClip(p.clip); const viewport = Viewport(); const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor); @@ -151,6 +199,11 @@ namespace Renderer { uFogColor: ValueCell.create(bgColor), uTransparentBackground: ValueCell.create(0), + uClipObjectType: ValueCell.create(clip.objects.type), + uClipObjectPosition: ValueCell.create(clip.objects.position), + uClipObjectRotation: ValueCell.create(clip.objects.rotation), + uClipObjectScale: ValueCell.create(clip.objects.scale), + // the following are general 'material' uniforms uLightIntensity: ValueCell.create(style.lightIntensity), uAmbientIntensity: ValueCell.create(style.ambientIntensity), @@ -177,6 +230,17 @@ namespace Renderer { return; } + let definesNeedUpdate = false; + if (r.values.dClipObjectCount.ref.value !== clip.objects.count) { + ValueCell.update(r.values.dClipObjectCount, clip.objects.count); + definesNeedUpdate = true; + } + if (r.values.dClipVariant.ref.value !== clip.variant) { + ValueCell.update(r.values.dClipVariant, clip.variant); + definesNeedUpdate = true; + } + if (definesNeedUpdate) r.update(); + const program = r.getProgram(variant); if (state.currentProgramId !== program.id) { // console.log('new program') @@ -341,6 +405,15 @@ namespace Renderer { ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness); ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity); } + + if (props.clip !== undefined && !deepEqual(props.clip, p.clip)) { + p.clip = props.clip; + Object.assign(clip, getClip(props.clip, clip)); + ValueCell.update(globalUniforms.uClipObjectPosition, clip.objects.position); + ValueCell.update(globalUniforms.uClipObjectRotation, clip.objects.rotation); + ValueCell.update(globalUniforms.uClipObjectScale, clip.objects.scale); + ValueCell.update(globalUniforms.uClipObjectType, clip.objects.type); + } }, setViewport: (x: number, y: number, width: number, height: number) => { gl.viewport(x, y, width, height); diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 60800bb555c94be447536d469d9940f895d2d625..d1665548dbd2cc2febb5bc885cb240e7d63d6317 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -34,6 +34,7 @@ import apply_fog from './shader/chunks/apply-fog.glsl'; import apply_interior_color from './shader/chunks/apply-interior-color.glsl'; import apply_light_color from './shader/chunks/apply-light-color.glsl'; import apply_marker_color from './shader/chunks/apply-marker-color.glsl'; +import assign_clipping_varying from './shader/chunks/assign-clipping-varying.glsl'; import assign_color_varying from './shader/chunks/assign-color-varying.glsl'; import assign_group from './shader/chunks/assign-group.glsl'; import assign_marker_varying from './shader/chunks/assign-marker-varying.glsl'; @@ -41,8 +42,11 @@ import assign_material_color from './shader/chunks/assign-material-color.glsl'; import assign_position from './shader/chunks/assign-position.glsl'; import assign_size from './shader/chunks/assign-size.glsl'; import check_picking_alpha from './shader/chunks/check-picking-alpha.glsl'; +import clip_instance from './shader/chunks/clip-instance.glsl'; +import clip_pixel from './shader/chunks/clip-pixel.glsl'; import color_frag_params from './shader/chunks/color-frag-params.glsl'; import color_vert_params from './shader/chunks/color-vert-params.glsl'; +import common_clip from './shader/chunks/common-clip.glsl'; import common_frag_params from './shader/chunks/common-frag-params.glsl'; import common_vert_params from './shader/chunks/common-vert-params.glsl'; import common from './shader/chunks/common.glsl'; @@ -59,6 +63,7 @@ const ShaderChunks: { [k: string]: string } = { apply_interior_color, apply_light_color, apply_marker_color, + assign_clipping_varying, assign_color_varying, assign_group, assign_marker_varying, @@ -66,8 +71,11 @@ const ShaderChunks: { [k: string]: string } = { assign_position, assign_size, check_picking_alpha, + clip_instance, + clip_pixel, color_frag_params, color_vert_params, + common_clip, common_frag_params, common_vert_params, common, diff --git a/src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts b/src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea54ba74c27688d65f5c8f6f639bba320c67db03 --- /dev/null +++ b/src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts @@ -0,0 +1,5 @@ +export default ` +#if dClipObjectCount != 0 && defined(dClipping) + vClipping = readFromTexture(tClipping, aInstance * float(uGroupCount) + group, uClippingTexDim).a; +#endif +`; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/assign-position.glsl.ts b/src/mol-gl/shader/chunks/assign-position.glsl.ts index 5631e47bd73cc7f34c4cb2c6904502a83a259e3b..36d775cf8fd5b7effe2a0e258349ffb0cc9d59a8 100644 --- a/src/mol-gl/shader/chunks/assign-position.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-position.glsl.ts @@ -1,11 +1,14 @@ export default ` -mat4 modelView = uView * uModel * aTransform; +mat4 model = uModel * aTransform; +mat4 modelView = uView * model; #ifdef dGeoTexture vec3 position = readFromTexture(tPositionGroup, aGroup, uGeoTexDim).xyz; #else vec3 position = aPosition; #endif -vec4 mvPosition = modelView * vec4(position, 1.0); +vec4 position4 = vec4(position, 1.0); +vModelPosition = (model * position4).xyz; +vec4 mvPosition = modelView * position4; vViewPosition = mvPosition.xyz; gl_Position = uProjection * mvPosition; `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/clip-instance.glsl.ts b/src/mol-gl/shader/chunks/clip-instance.glsl.ts new file mode 100644 index 0000000000000000000000000000000000000000..163c76f0f6afc8091aa81ed5fdc1861dabb51f13 --- /dev/null +++ b/src/mol-gl/shader/chunks/clip-instance.glsl.ts @@ -0,0 +1,13 @@ +export default ` +#if defined(dClipVariant_instance) && dClipObjectCount != 0 + int flag = 0; + #if defined(dClipping) + flag = int(floor(vClipping * 255.0 + 0.5)); + #endif + + vec4 mCenter = model * vec4(uInvariantBoundingSphere.xyz, 1.0); + if (clipTest(vec4(mCenter.xyz, uInvariantBoundingSphere.w), flag)) + // move out of [ -w, +w ] to 'discard' in vert shader + gl_Position.z = 2.0 * gl_Position.w; +#endif +`; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/clip-pixel.glsl.ts b/src/mol-gl/shader/chunks/clip-pixel.glsl.ts new file mode 100644 index 0000000000000000000000000000000000000000..ad6affa9cec5ae93f0f1a9a82dae9219614f5470 --- /dev/null +++ b/src/mol-gl/shader/chunks/clip-pixel.glsl.ts @@ -0,0 +1,11 @@ +export default ` +#if defined(dClipVariant_pixel) && dClipObjectCount != 0 + int flag = 0; + #if defined(dClipping) + flag = int(floor(vClipping * 255.0 + 0.5)); + #endif + + if (clipTest(vec4(vModelPosition, 0.0), flag)) + discard; +#endif +`; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-clip.glsl.ts b/src/mol-gl/shader/chunks/common-clip.glsl.ts new file mode 100644 index 0000000000000000000000000000000000000000..444ecddc4a54e069722207540ad33ad1050c8f94 --- /dev/null +++ b/src/mol-gl/shader/chunks/common-clip.glsl.ts @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Ludovic Autin <autin@scripps.edu> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export default ` +#if dClipObjectCount != 0 + vec3 quaternionTransform(vec4 q, vec3 v) { + vec3 t = 2.0 * cross(q.xyz, v); + return v + q.w * t + cross(q.xyz, t); + } + + vec4 computePlane(vec3 normal, vec3 inPoint) { + return vec4(normalize(normal), -dot(normal, inPoint)); + } + + float planeSD(vec4 plane, vec3 center) { + return -dot(plane.xyz, center - plane.xyz * -plane.w); + } + + float sphereSD(vec3 position, vec4 rotation, vec3 size, vec3 center) { + return ( + length(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position) / size) - 1.0 + ) * min(min(size.x, size.y), size.z); + } + + float cubeSD(vec3 position, vec4 rotation, vec3 size, vec3 center) { + vec3 d = abs(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position)) - size; + return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0)); + } + + float cylinderSD(vec3 position, vec4 rotation, vec3 size, vec3 center) { + vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position); + + vec2 d = abs(vec2(length(t.xz), t.y)) - size.xy; + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)); + } + + float infiniteConeSD(vec3 position, vec4 rotation, vec3 size, vec3 center) { + vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position); + + float q = length(t.xy); + return dot(size.xy, vec2(q, t.z)); + } + + float getSignedDistance(vec3 center, int type, vec3 position, vec4 rotation, vec3 scale) { + if (type == 1) { + vec3 normal = quaternionTransform(rotation, vec3(0.0, 1.0, 0.0)); + vec4 plane = computePlane(normal, position); + return planeSD(plane, center); + } else if (type == 2) { + return sphereSD(position, rotation, scale * 0.5, center); + } else if (type == 3) { + return cubeSD(position, rotation, scale * 0.5, center); + } else if (type == 4) { + return cylinderSD(position, rotation, scale * 0.5, center); + } else if (type == 5) { + return infiniteConeSD(position, rotation, scale * 0.5, center); + } else { + return 0.1; + } + } + + #if __VERSION__ != 300 + // 8-bit + int bitwiseAnd(int a, int b) { + int d = 128; + int result = 0; + for (int i = 0; i < 8; ++i) { + if (d <= 0) break; + if (a >= d && b >= d) result += d; + if (a >= d) a -= d; + if (b >= d) b -= d; + d /= 2; + } + return result; + } + + bool hasBit(int mask, int bit) { + return bitwiseAnd(mask, bit) == 0; + } + #else + bool hasBit(int mask, int bit) { + return (mask & bit) == 0; + } + #endif + + // flag is a bit-flag for clip-objects to ignore (note, object ids start at 1 not 0) + bool clipTest(vec4 sphere, int flag) { + for (int i = 0; i < dClipObjectCount; ++i) { + if (flag == 0 || hasBit(flag, i + 1)) { + // TODO take sphere radius into account? + if (getSignedDistance(sphere.xyz, uClipObjectType[i], uClipObjectPosition[i], uClipObjectRotation[i], uClipObjectScale[i]) <= 0.0) + return true; + } + } + return false; + } +#endif +`; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts index dce8c6fa0690045c1c505446c0eacf0b743290c4..a2aeacf42bbe9391077ed8d9bb5208bdd6c3b3d1 100644 --- a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts +++ b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts @@ -3,6 +3,21 @@ uniform int uObjectId; uniform int uInstanceCount; uniform int uGroupCount; +#if dClipObjectCount != 0 + uniform int uClipObjectType[dClipObjectCount]; + uniform vec3 uClipObjectPosition[dClipObjectCount]; + uniform vec4 uClipObjectRotation[dClipObjectCount]; + uniform vec3 uClipObjectScale[dClipObjectCount]; + + #if defined(dClipping) + #if __VERSION__ != 300 + varying float vClipping; + #else + flat in float vClipping; + #endif + #endif +#endif + uniform vec3 uHighlightColor; uniform vec3 uSelectColor; #if __VERSION__ != 300 @@ -11,6 +26,7 @@ uniform vec3 uSelectColor; flat in float vMarker; #endif +varying vec3 vModelPosition; varying vec3 vViewPosition; uniform vec2 uViewOffset; diff --git a/src/mol-gl/shader/chunks/common-vert-params.glsl.ts b/src/mol-gl/shader/chunks/common-vert-params.glsl.ts index d5d018f8a3228e631b6bcc66632ad401c4409056..a8c8eed90c404db375de914fa7882fc444aa9ac6 100644 --- a/src/mol-gl/shader/chunks/common-vert-params.glsl.ts +++ b/src/mol-gl/shader/chunks/common-vert-params.glsl.ts @@ -5,6 +5,24 @@ uniform vec3 uCameraPosition; uniform int uObjectId; uniform int uInstanceCount; uniform int uGroupCount; +uniform vec4 uInvariantBoundingSphere; + +#if dClipObjectCount != 0 + uniform int uClipObjectType[dClipObjectCount]; + uniform vec3 uClipObjectPosition[dClipObjectCount]; + uniform vec4 uClipObjectRotation[dClipObjectCount]; + uniform vec3 uClipObjectScale[dClipObjectCount]; + + #if defined(dClipping) + uniform vec2 uClippingTexDim; + uniform sampler2D tClipping; + #if __VERSION__ != 300 + varying float vClipping; + #else + flat out float vClipping; + #endif + #endif +#endif uniform vec2 uMarkerTexDim; uniform sampler2D tMarker; @@ -14,5 +32,6 @@ uniform sampler2D tMarker; flat out float vMarker; #endif +varying vec3 vModelPosition; varying vec3 vViewPosition; `; \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.frag.ts b/src/mol-gl/shader/mesh.frag.ts index 10e1752302a6f308797e0ee88c5a064aeded8ee4..0ed9f7ebe318da42c6fa91bea2d1d1199fa575c4 100644 --- a/src/mol-gl/shader/mesh.frag.ts +++ b/src/mol-gl/shader/mesh.frag.ts @@ -13,8 +13,11 @@ precision highp int; #include color_frag_params #include light_frag_params #include normal_frag_params +#include common_clip void main() { + #include clip_pixel + // Workaround for buggy gl_FrontFacing (e.g. on some integrated Intel GPUs) #if defined(enabledStandardDerivatives) vec3 fdx = dFdx(vViewPosition); diff --git a/src/mol-gl/shader/mesh.vert.ts b/src/mol-gl/shader/mesh.vert.ts index e00073548c71d2749d4e32de79ada8531f3a43d4..1117ccb5952867ee6897dc9b78d28e650862c6ee 100644 --- a/src/mol-gl/shader/mesh.vert.ts +++ b/src/mol-gl/shader/mesh.vert.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -12,6 +12,7 @@ precision highp int; #include read_from_texture #include common_vert_params #include color_vert_params +#include common_clip #ifdef dGeoTexture uniform vec2 uGeoTexDim; @@ -34,7 +35,9 @@ void main(){ #include assign_group #include assign_color_varying #include assign_marker_varying + #include assign_clipping_varying #include assign_position + #include clip_instance #ifdef dGeoTexture vec3 normal = readFromTexture(tNormal, aGroup, uGeoTexDim).xyz; diff --git a/src/mol-gl/shader/text.vert.ts b/src/mol-gl/shader/text.vert.ts index 053815bd194600340999bc9ee8cced723244cac2..0d38fc3d96cdb6f4005c08f4182c9d01a4dc4f5b 100644 --- a/src/mol-gl/shader/text.vert.ts +++ b/src/mol-gl/shader/text.vert.ts @@ -64,7 +64,7 @@ void main(void){ vec4 mvCorner = vec4(mvPosition.xyz, 1.0); if (vTexCoord.x == 10.0) { // indicates background plane - // move a bit to the back, tkaing ditsnace to camera into account to avoid z-fighting + // move a bit to the back, taking distance to camera into account to avoid z-fighting offsetZ -= 0.001 * distance(uCameraPosition, (uProjection * mvCorner).xyz); } diff --git a/src/mol-gl/webgl/program.ts b/src/mol-gl/webgl/program.ts index a36d4c8dc45d90b39b05e9c4d0bac3843219d80c..8f67d8ce062f3a1d8a1231cf4095467619c742b4 100644 --- a/src/mol-gl/webgl/program.ts +++ b/src/mol-gl/webgl/program.ts @@ -86,7 +86,8 @@ function checkActiveUniforms(gl: GLRenderingContext, program: WebGLProgram, sche // name assigned by `gl.shim.ts`, ignore for checks continue; } - const spec = schema[name]; + const baseName = name.replace(/[[0-9]+\]$/, ''); // 'array' uniforms + const spec = schema[baseName]; if (spec === undefined) { throw new Error(`missing 'uniform' or 'texture' with name '${name}' in schema`); } diff --git a/src/mol-gl/webgl/uniform.ts b/src/mol-gl/webgl/uniform.ts index 8fa7d51e09606660f6a37f7fa6b2bf516dfd79a0..f3e6783e37d365c1ea0096455acebde9276b24c8 100644 --- a/src/mol-gl/webgl/uniform.ts +++ b/src/mol-gl/webgl/uniform.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -8,45 +8,33 @@ import { Mat3, Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra'; import { ValueCell } from '../../mol-util'; import { GLRenderingContext } from './compat'; import { RenderableSchema } from '../../mol-gl/renderable/schema'; +import { ValueOf } from '../../mol-util/type-helpers'; export type UniformKindValue = { - 'f': number - 'i': number - 'v2': Vec2 - 'v3': Vec3 - 'v4': Vec4 - 'm3': Mat3 - 'm4': Mat4 - 't': number + 'f': number; 'f[]': number[] + 'i': number; 'i[]': number[] + 'v2': Vec2; 'v2[]': number[] + 'v3': Vec3; 'v3[]': number[] + 'v4': Vec4; 'v4[]': number[] + 'm3': Mat3; 'm3[]': number[] + 'm4': Mat4; 'm4[]': number[] + 't': number; 't[]': number[] } export type UniformKind = keyof UniformKindValue -export type UniformType = number | Vec2 | Vec3 | Vec4 | Mat3 | Mat4 +export type UniformType = ValueOf<UniformKindValue> export type UniformValues = { [k: string]: ValueCell<UniformType> } export type UniformsList = [string, ValueCell<UniformType>][] export function getUniformType(gl: GLRenderingContext, kind: UniformKind) { switch (kind) { - case 'f': return gl.FLOAT; - case 'i': return gl.INT; - case 'v2': return gl.FLOAT_VEC2; - case 'v3': return gl.FLOAT_VEC3; - case 'v4': return gl.FLOAT_VEC4; - case 'm3': return gl.FLOAT_MAT3; - case 'm4': return gl.FLOAT_MAT4; - default: console.error(`unknown uniform kind '${kind}'`); - } -} - -export function setUniform(gl: GLRenderingContext, location: WebGLUniformLocation | null, kind: UniformKind, value: any) { - switch (kind) { - case 'f': gl.uniform1f(location, value); break; - case 'i': case 't': gl.uniform1i(location, value); break; - case 'v2': gl.uniform2fv(location, value); break; - case 'v3': gl.uniform3fv(location, value); break; - case 'v4': gl.uniform4fv(location, value); break; - case 'm3': gl.uniformMatrix3fv(location, false, value); break; - case 'm4': gl.uniformMatrix4fv(location, false, value); break; + case 'f': case 'f[]': return gl.FLOAT; + case 'i': case 'i[]': return gl.INT; + case 'v2': case 'v2[]': return gl.FLOAT_VEC2; + case 'v3': case 'v3[]': return gl.FLOAT_VEC3; + case 'v4': case 'v4[]': return gl.FLOAT_VEC4; + case 'm3': case 'm3[]': return gl.FLOAT_MAT3; + case 'm4': case 'm4[]': return gl.FLOAT_MAT4; default: console.error(`unknown uniform kind '${kind}'`); } } @@ -55,24 +43,27 @@ export type UniformSetter = (gl: GLRenderingContext, location: number, value: an export type UniformSetters = { [k: string]: UniformSetter } function uniform1f (gl: GLRenderingContext, location: number, value: any) { gl.uniform1f(location, value); } +function uniform1fv (gl: GLRenderingContext, location: number, value: any) { gl.uniform1fv(location, value); } function uniform1i (gl: GLRenderingContext, location: number, value: any) { gl.uniform1i(location, value); } +function uniform1iv (gl: GLRenderingContext, location: number, value: any) { gl.uniform1iv(location, value); } function uniform2fv (gl: GLRenderingContext, location: number, value: any) { gl.uniform2fv(location, value); } function uniform3fv (gl: GLRenderingContext, location: number, value: any) { gl.uniform3fv(location, value); } function uniform4fv (gl: GLRenderingContext, location: number, value: any) { gl.uniform4fv(location, value); } function uniformMatrix3fv (gl: GLRenderingContext, location: number, value: any) { gl.uniformMatrix3fv(location, false, value); } function uniformMatrix4fv (gl: GLRenderingContext, location: number, value: any) { gl.uniformMatrix4fv(location, false, value); } -function getUniformSetter(kind: UniformKind) { +function getUniformSetter(kind: UniformKind): UniformSetter { switch (kind) { case 'f': return uniform1f; + case 'f[]': return uniform1fv; case 'i': case 't': return uniform1i; - case 'v2': return uniform2fv; - case 'v3': return uniform3fv; - case 'v4': return uniform4fv; - case 'm3': return uniformMatrix3fv; - case 'm4': return uniformMatrix4fv; + case 'i[]': case 't[]': return uniform1iv; + case 'v2': case 'v2[]': return uniform2fv; + case 'v3': case 'v3[]': return uniform3fv; + case 'v4': case 'v4[]': return uniform4fv; + case 'm3': case 'm3[]': return uniformMatrix3fv; + case 'm4': case 'm4[]': return uniformMatrix4fv; } - throw new Error(`unknown uniform kind '${kind}'`); } export function getUniformSetters(schema: RenderableSchema) { diff --git a/src/mol-math/linear-algebra/3d/vec4.ts b/src/mol-math/linear-algebra/3d/vec4.ts index fa98ff02cf529d654159d2a61582fc57cd2a205a..14ef67b9078f65d752345b21b96d1be6e08ad722 100644 --- a/src/mol-math/linear-algebra/3d/vec4.ts +++ b/src/mol-math/linear-algebra/3d/vec4.ts @@ -20,6 +20,7 @@ import Mat4 from './mat4'; import { EPSILON } from '../3d'; import { NumberArray } from '../../../mol-util/type-helpers'; +import { Sphere3D } from '../../geometry/primitives/sphere3d'; interface Vec4 extends Array<number> { [d: number]: number, '@type': 'vec4', length: 4 } @@ -53,6 +54,18 @@ namespace Vec4 { return out; } + export function fromSphere(out: Vec4, sphere: Sphere3D) { + out[0] = sphere.center[0]; + out[1] = sphere.center[1]; + out[2] = sphere.center[2]; + out[3] = sphere.radius; + return out; + } + + export function ofSphere(sphere: Sphere3D) { + return fromSphere(zero(), sphere); + } + export function hasNaN(a: Vec4) { return isNaN(a[0]) || isNaN(a[1]) || isNaN(a[2]) || isNaN(a[3]); } diff --git a/src/mol-plugin-state/helpers/structure-clipping.ts b/src/mol-plugin-state/helpers/structure-clipping.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e6d8e2907f6d65eb25a75a3c2689d7f41534908 --- /dev/null +++ b/src/mol-plugin-state/helpers/structure-clipping.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2019-2020 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> + */ + +import { Structure, StructureElement } from '../../mol-model/structure'; +import { PluginStateObject } from '../../mol-plugin-state/objects'; +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 { 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>>) => void +const ClippingManagerTag = 'clipping-controls'; + +export async function setStructureClipping(plugin: PluginContext, components: StructureComponentRef[], groups: Clipping.Groups, lociGetter: (structure: Structure) => StructureElement.Loci | EmptyLoci, types?: string[]) { + await eachRepr(plugin, components, (update, repr, clippingCell) => { + if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return; + + const structure = repr.obj!.data.source.data; + // 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 = lociGetter(structure.root); + if (Loci.isEmpty(loci)) return; + + const layer = { + bundle: StructureElement.Bundle.fromLoci(loci), + groups + }; + + if (clippingCell) { + const bundleLayers = [...clippingCell.params!.values.layers, layer]; + const filtered = getFilteredBundle(bundleLayers, structure); + update.to(clippingCell).update(Clipping.toBundle(filtered)); + } else { + const filtered = getFilteredBundle([layer], structure); + update.to(repr.transform.ref) + .apply(StateTransforms.Representation.ClippingStructureRepresentation3DFromBundle, Clipping.toBundle(filtered), { tags: ClippingManagerTag }); + } + }); +} + +function eachRepr(plugin: PluginContext, components: StructureComponentRef[], callback: ClippingEachReprCallback) { + const state = plugin.state.data; + const update = state.build(); + for (const c of components) { + for (const r of c.representations) { + const clipping = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.ClippingStructureRepresentation3DFromBundle, r.cell.transform.ref).withTag(ClippingManagerTag)); + callback(update, r.cell, clipping[0]); + } + } + + return update.commit({ doNotUpdateCurrent: true }); +} + +/** filter clipping layers for given structure */ +function getFilteredBundle(layers: Clipping.BundleLayer[], structure: Structure) { + const clipping = Clipping.ofBundle(layers, structure.root); + const merged = Clipping.merge(clipping); + return Clipping.filter(merged, structure); +} \ No newline at end of file diff --git a/src/mol-plugin-state/manager/structure/component.ts b/src/mol-plugin-state/manager/structure/component.ts index 0e8387a93b0e4d5059ec9b3d41126cad802133db..938604a1ce25ecd76258a8d503338977cc676d4c 100644 --- a/src/mol-plugin-state/manager/structure/component.ts +++ b/src/mol-plugin-state/manager/structure/component.ts @@ -26,6 +26,8 @@ import { createStructureColorThemeParams, createStructureSizeThemeParams } from import { StructureSelectionQueries, StructureSelectionQuery } from '../../helpers/structure-selection-query'; import { StructureRepresentation3D } from '../../transforms/representation'; import { StructureHierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state'; +import { Clipping } from '../../../mol-theme/clipping'; +import { setStructureClipping } from '../../helpers/structure-clipping'; export { StructureComponentManager }; @@ -358,7 +360,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone }, { canUndo: 'Add Selection' }); } - async applyColor(params: StructureComponentManager.ColorParams, structures?: ReadonlyArray<StructureRef>) { + async applyTheme(params: StructureComponentManager.ThemeParams, structures?: ReadonlyArray<StructureRef>) { return this.plugin.dataTransaction(async () => { const xs = structures || this.currentStructures; if (xs.length === 0) return; @@ -367,12 +369,15 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone for (const s of xs) { if (params.action.name === 'reset') { await clearStructureOverpaint(this.plugin, s.components, params.representations); - } else { + } else if (params.action.name === 'color') { const p = params.action.params; await setStructureOverpaint(this.plugin, s.components, p.color, getLoci, params.representations, p.opacity); + } else if (params.action.name === 'clipping') { + const p = params.action.params; + await setStructureClipping(this.plugin, s.components, Clipping.Groups.fromNames(p.excludeGroups), getLoci, params.representations); } } - }, { canUndo: 'Apply Color' }); + }, { canUndo: 'Apply Theme' }); } private modifyComponent(builder: StateBuilder.Root, component: StructureComponentRef, by: Structure, action: StructureComponentManager.ModifyAction) { @@ -448,19 +453,22 @@ namespace StructureComponentManager { } export type AddParams = { selection: StructureSelectionQuery, options: { checkExisting: boolean, label: string }, representation: string } - export function getColorParams(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) { + export function getThemeParams(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) { return { action: PD.MappedStatic('color', { color: PD.Group({ color: PD.Color(ColorNames.blue, { isExpanded: true }), opacity: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }), }, { isFlat: true }), - reset: PD.EmptyGroup() + reset: PD.EmptyGroup(), + clipping: PD.Group({ + excludeGroups: PD.MultiSelect([] as Clipping.Groups.Names[], PD.objectToOptions(Clipping.Groups.Names)), + }, { isFlat: true }), }), representations: PD.MultiSelect([], getRepresentationTypes(plugin, pivot), { emptyValue: 'All' }) }; } - export type ColorParams = PD.Values<ReturnType<typeof getColorParams>> + export type ThemeParams = PD.Values<ReturnType<typeof getThemeParams>> export function getRepresentationTypes(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) { return pivot?.cell.obj?.data diff --git a/src/mol-plugin-state/transforms/representation.ts b/src/mol-plugin-state/transforms/representation.ts index bcb1f8560550c73feba8220fa5b5cd7ac932c95b..14dbadb815994731bd5bc9ac3b58a6673097331c 100644 --- a/src/mol-plugin-state/transforms/representation.ts +++ b/src/mol-plugin-state/transforms/representation.ts @@ -34,6 +34,8 @@ import { OrientationRepresentation, OrientationParams } from '../../mol-repr/sha import { AngleParams, AngleRepresentation } from '../../mol-repr/shape/loci/angle'; import { DihedralParams, DihedralRepresentation } from '../../mol-repr/shape/loci/dihedral'; import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry'; +import { Clipping } from '../../mol-theme/clipping'; +import { ObjectKeys } from '../../mol-util/type-helpers'; export { StructureRepresentation3D }; export { ExplodeStructureRepresentation3D }; @@ -42,6 +44,8 @@ export { OverpaintStructureRepresentation3DFromScript }; export { OverpaintStructureRepresentation3DFromBundle }; export { TransparencyStructureRepresentation3DFromScript }; export { TransparencyStructureRepresentation3DFromBundle }; +export { ClippingStructureRepresentation3DFromScript }; +export { ClippingStructureRepresentation3DFromBundle }; export { VolumeRepresentation3D }; type StructureRepresentation3D = typeof StructureRepresentation3D @@ -438,6 +442,99 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui } }); +type ClippingStructureRepresentation3DFromScript = typeof ClippingStructureRepresentation3DFromScript +const ClippingStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn({ + name: 'clipping-structure-representation-3d-from-script', + display: 'Clipping 3D Representation', + from: SO.Molecule.Structure.Representation3D, + to: SO.Molecule.Structure.Representation3DState, + params: { + layers: PD.ObjectList({ + script: PD.Script(Script('(sel.atom.all)', 'mol-script')), + groups: PD.Converted((g: Clipping.Groups) => Clipping.Groups.toNames(g), n => Clipping.Groups.fromNames(n), PD.MultiSelect(ObjectKeys(Clipping.Groups.Names), PD.objectToOptions(Clipping.Groups.Names))), + }, e => `${Clipping.Groups.toNames(e.groups).length} group(s)`, { + defaultValue: [{ + script: Script('(sel.atom.all)', 'mol-script'), + groups: Clipping.Groups.Flag.None, + }] + }), + } +})({ + canAutoUpdate() { + return true; + }, + apply({ a, params }) { + const structure = a.data.source.data; + const clipping = Clipping.ofScript(params.layers, structure); + + return new SO.Molecule.Structure.Representation3DState({ + state: { clipping }, + initialState: { clipping: Clipping.Empty }, + info: structure, + source: a + }, { label: `Clipping (${clipping.layers.length} Layers)` }); + }, + update({ a, b, newParams, oldParams }) { + const structure = b.data.info as Structure; + if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate; + const oldClipping = b.data.state.clipping!; + const newClipping = Clipping.ofScript(newParams.layers, structure); + if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged; + + b.data.state.clipping = newClipping; + b.data.source = a; + b.label = `Clipping (${newClipping.layers.length} Layers)`; + return StateTransformer.UpdateResult.Updated; + } +}); + +type ClippingStructureRepresentation3DFromBundle = typeof ClippingStructureRepresentation3DFromBundle +const ClippingStructureRepresentation3DFromBundle = PluginStateTransform.BuiltIn({ + name: 'clipping-structure-representation-3d-from-bundle', + display: 'Clipping 3D Representation', + from: SO.Molecule.Structure.Representation3D, + to: SO.Molecule.Structure.Representation3DState, + params: { + layers: PD.ObjectList({ + bundle: PD.Value<StructureElement.Bundle>(StructureElement.Bundle.Empty), + groups: PD.Converted((g: Clipping.Groups) => Clipping.Groups.toNames(g), n => Clipping.Groups.fromNames(n), PD.MultiSelect(ObjectKeys(Clipping.Groups.Names), PD.objectToOptions(Clipping.Groups.Names))), + }, e => `${Clipping.Groups.toNames(e.groups).length} group(s)`, { + defaultValue: [{ + bundle: StructureElement.Bundle.Empty, + groups: Clipping.Groups.Flag.None, + }], + isHidden: true + }), + } +})({ + canAutoUpdate() { + return true; + }, + apply({ a, params }) { + const structure = a.data.source.data; + const clipping = Clipping.ofBundle(params.layers, structure); + + return new SO.Molecule.Structure.Representation3DState({ + state: { clipping }, + initialState: { clipping: Clipping.Empty }, + info: structure, + source: a + }, { label: `Clipping (${clipping.layers.length} Layers)` }); + }, + update({ a, b, newParams, oldParams }) { + const structure = b.data.info as Structure; + if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate; + const oldClipping = b.data.state.clipping!; + const newClipping = Clipping.ofBundle(newParams.layers, structure); + if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged; + + b.data.state.clipping = newClipping; + b.data.source = a; + b.label = `Clipping (${newClipping.layers.length} Layers)`; + return StateTransformer.UpdateResult.Updated; + } +}); + // export namespace VolumeRepresentation3DHelpers { diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx index 58781714fdb56a322e1eeff60ec5cc20252b7eeb..0b682a9b57c87997034b3921e28ac0bfd2a7ebbc 100644 --- a/src/mol-plugin-ui/controls/parameters.tsx +++ b/src/mol-plugin-ui/controls/parameters.tsx @@ -1093,7 +1093,7 @@ class ObjectListItem extends React.PureComponent<ObjectListItemProps, { isExpand state = { isExpanded: false }; update = (v: object) => { - this.setState({ isExpanded: false }); + // this.setState({ isExpanded: false }); // TODO auto update? mark changed state? this.props.actions.update(v, this.props.index); } diff --git a/src/mol-plugin-ui/structure/selection.tsx b/src/mol-plugin-ui/structure/selection.tsx index ec8728301c522a8e437be976348b99e1fa96199b..941dff2aa48720d44495e06b97029166964782fe 100644 --- a/src/mol-plugin-ui/structure/selection.tsx +++ b/src/mol-plugin-ui/structure/selection.tsx @@ -43,7 +43,6 @@ export class ToggleSelectionModeButton extends PurePluginUIComponent<{ inline?: } } - const StructureSelectionParams = { granularity: InteractivityManager.Params.granularity, }; @@ -53,7 +52,7 @@ interface StructureSelectionActionsControlsState { isBusy: boolean, canUndo: boolean, - action?: StructureSelectionModifier | 'color' | 'add-repr' | 'help' + action?: StructureSelectionModifier | 'theme' | 'add-repr' | 'help' } const ActionHeader = new Map<StructureSelectionModifier, string>([ @@ -159,7 +158,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str toggleRemove = this.showAction('remove') toggleIntersect = this.showAction('intersect') toggleSet = this.showAction('set') - toggleColor = this.showAction('color') + toggleTheme = this.showAction('theme') toggleAddRepr = this.showAction('add-repr') toggleHelp = this.showAction('help') @@ -196,7 +195,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str <ToggleButton icon={IntersectSvg} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} /> <ToggleButton icon={SetSvg} title={`${ActionHeader.get('set')}. Hold shift key to keep menu open.`} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} /> - <ToggleButton icon={BrushSvg} title='Color Selection' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} /> + <ToggleButton icon={BrushSvg} title='Apply Theme to Selection' toggle={this.toggleTheme} isSelected={this.state.action === 'theme'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} /> <ToggleButton icon={CubeSvg} title='Create Representation of Selection' toggle={this.toggleAddRepr} isSelected={this.state.action === 'add-repr'} disabled={this.isDisabled} /> <IconButton svg={RemoveSvg} title='Subtract Selection from Representations' onClick={this.subtract} disabled={this.isDisabled} /> <IconButton svg={RestoreSvg} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} /> @@ -204,12 +203,12 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str <ToggleButton icon={HelpOutlineSvg} title='Show/hide help' toggle={this.toggleHelp} style={{ marginLeft: '10px' }} isSelected={this.state.action === 'help'} /> <IconButton svg={CancelOutlinedSvg} title='Turn selection mode off' onClick={this.turnOff} /> </div> - {(this.state.action && this.state.action !== 'color' && this.state.action !== 'add-repr' && this.state.action !== 'help') && <div className='msp-selection-viewport-controls-actions'> + {(this.state.action && this.state.action !== 'theme' && this.state.action !== 'add-repr' && this.state.action !== 'help') && <div className='msp-selection-viewport-controls-actions'> <ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} title='Click to close.' items={this.queries} onSelect={this.selectQuery} noOffset /> </div>} - {this.state.action === 'color' && <div className='msp-selection-viewport-controls-actions'> - <ControlGroup header='Color' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor} topRightIcon={CloseSvg}> - <ApplyColorControls onApply={this.toggleColor} /> + {this.state.action === 'theme' && <div className='msp-selection-viewport-controls-actions'> + <ControlGroup header='Theme' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleTheme} topRightIcon={CloseSvg}> + <ApplyThemeControls onApply={this.toggleTheme} /> </ControlGroup> </div>} {this.state.action === 'add-repr' && <div className='msp-selection-viewport-controls-actions'> @@ -306,21 +305,21 @@ export class StructureSelectionStatsControls extends PluginUIComponent<{ hideOnE } interface ApplyColorControlsState { - values: StructureComponentManager.ColorParams + values: StructureComponentManager.ThemeParams } interface ApplyColorControlsProps { onApply?: () => void } -class ApplyColorControls extends PurePluginUIComponent<ApplyColorControlsProps, ApplyColorControlsState> { - _params = memoizeLatest((pivot: StructureRef | undefined) => StructureComponentManager.getColorParams(this.plugin, pivot)); +class ApplyThemeControls extends PurePluginUIComponent<ApplyColorControlsProps, ApplyColorControlsState> { + _params = memoizeLatest((pivot: StructureRef | undefined) => StructureComponentManager.getThemeParams(this.plugin, pivot)); get params() { return this._params(this.plugin.managers.structure.component.pivotStructure); } state = { values: ParamDefinition.getDefaultValues(this.params) }; apply = () => { - this.plugin.managers.structure.component.applyColor(this.state.values); + this.plugin.managers.structure.component.applyTheme(this.state.values); this.props.onApply?.(); } @@ -330,7 +329,7 @@ class ApplyColorControls extends PurePluginUIComponent<ApplyColorControlsProps, return <> <ParameterControls params={this.params} values={this.state.values} onChangeValues={this.paramsChanged} /> <Button icon={BrushSvg} className='msp-btn-commit msp-btn-commit-on' onClick={this.apply} style={{ marginTop: '1px' }}> - Apply Coloring + Apply Theme </Button> </>; } diff --git a/src/mol-plugin-ui/viewport/simple-settings.tsx b/src/mol-plugin-ui/viewport/simple-settings.tsx index 7952d669daa2f8d36452d7b94ff7d92d05918a82..cf72e5002ca9ab640d781007e1340666d2718a7c 100644 --- a/src/mol-plugin-ui/viewport/simple-settings.tsx +++ b/src/mol-plugin-ui/viewport/simple-settings.tsx @@ -62,7 +62,10 @@ const SimpleSettingsParams = { outline: Canvas3DParams.postprocessing.params.outline, fog: Canvas3DParams.cameraFog, }, { pivot: 'renderStyle' }), - clipping: Canvas3DParams.cameraClipping, + clipping: PD.Group<any>({ + ...Canvas3DParams.cameraClipping.params, + ...(Canvas3DParams.renderer.params.clip as any).params as any + }, { pivot: 'radius' }), layout: PD.MultiSelect([] as LayoutOptions[], PD.objectToOptions(LayoutOptions)), }; @@ -108,7 +111,10 @@ const SimpleSettingsMapping = ParamMapping({ outline: canvas.postprocessing.outline, fog: canvas.cameraFog }, - clipping: canvas.cameraClipping + clipping: { + ...canvas.cameraClipping, + ...canvas.renderer.clip + } }; }, update(s, props) { @@ -122,7 +128,14 @@ const SimpleSettingsMapping = ParamMapping({ canvas.postprocessing.occlusion = s.lighting.occlusion; canvas.postprocessing.outline = s.lighting.outline; canvas.cameraFog = s.lighting.fog; - canvas.cameraClipping = s.clipping; + canvas.cameraClipping = { + radius: s.clipping.radius, + far: s.clipping.far, + }; + canvas.renderer.clip = { + variant: s.clipping.variant, + objects: s.clipping.objects, + }; props.layout = s.layout; }, diff --git a/src/mol-repr/representation.ts b/src/mol-repr/representation.ts index cd34ef28cf4c81acba6bb95d57a5646c301513d4..0a220ef81dd8f8d8cd2001ae283ad9c03584389f 100644 --- a/src/mol-repr/representation.ts +++ b/src/mol-repr/representation.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -22,6 +22,7 @@ import { getQualityProps } from './util'; import { BaseGeometry } from '../mol-geo/geometry/base'; import { Visual } from './visual'; import { CustomProperty } from '../mol-model-props/common/custom-property'; +import { Clipping } from '../mol-theme/clipping'; // export interface RepresentationProps { // visuals?: string[] @@ -173,6 +174,8 @@ namespace Representation { overpaint: Overpaint /** Per group transparency applied to the representation's renderobjects */ transparency: Transparency + /** Bit mask of per group clipping applied to the representation's renderobjects */ + clipping: Clipping /** Controls if the representation's renderobjects are synced automatically with GPU or not */ syncManually: boolean /** A transformation applied to the representation's renderobjects */ @@ -181,7 +184,7 @@ namespace Representation { markerActions: MarkerActions } export function createState(): State { - return { visible: true, alphaFactor: 1, pickable: true, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty, transparency: Transparency.Empty, markerActions: MarkerActions.All }; + return { visible: true, alphaFactor: 1, pickable: true, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty, transparency: Transparency.Empty, clipping: Clipping.Empty, markerActions: MarkerActions.All }; } export function updateState(state: State, update: Partial<State>) { if (update.visible !== undefined) state.visible = update.visible; @@ -189,6 +192,7 @@ namespace Representation { if (update.pickable !== undefined) state.pickable = update.pickable; if (update.overpaint !== undefined) state.overpaint = update.overpaint; if (update.transparency !== undefined) state.transparency = update.transparency; + if (update.clipping !== undefined) state.clipping = update.clipping; if (update.syncManually !== undefined) state.syncManually = update.syncManually; if (update.transform !== undefined) Mat4.copy(state.transform, update.transform); if (update.markerActions !== undefined) state.markerActions = update.markerActions; diff --git a/src/mol-repr/structure/complex-representation.ts b/src/mol-repr/structure/complex-representation.ts index 359e0d0be8f1886e186c5d68f34081d711e03afc..bae3c56ddf1499a55f8c3ea8c836a3331bf4fc21 100644 --- a/src/mol-repr/structure/complex-representation.ts +++ b/src/mol-repr/structure/complex-representation.ts @@ -18,6 +18,7 @@ import { EmptyLoci, Loci, isEveryLoci, isDataLoci } from '../../mol-model/loci'; import { MarkerAction, MarkerActions } from '../../mol-util/marker-action'; import { Overpaint } from '../../mol-theme/overpaint'; import { StructureParams } from './params'; +import { Clipping } from '../../mol-theme/clipping'; export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number) => ComplexVisual<P>): StructureRepresentation<P> { let version = 0; @@ -94,6 +95,11 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, visual.setOverpaint(remappedOverpaint); } if (state.transparency !== undefined && visual) visual.setTransparency(state.transparency); + if (state.clipping !== undefined && visual) { + // Remap loci from equivalent structure to the current structure + const remappedClipping = Clipping.remap(state.clipping, _structure); + visual.setClipping(remappedClipping); + } if (state.transform !== undefined && visual) visual.setTransform(state.transform); if (state.unitTransforms !== undefined && visual) { // Since ComplexVisuals always renders geometries between units, the application diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index da7113123ad89a9cdbad22cf96d3712827e44772..7e695c0eba45bc5f9e9f343286e746d0c305daf9 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/src/mol-repr/structure/complex-visual.ts @@ -30,6 +30,7 @@ import { SizeTheme } from '../../mol-theme/size'; import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume'; import { createMarkers } from '../../mol-geo/geometry/marker-data'; import { StructureParams, StructureMeshParams, StructureTextParams, StructureDirectVolumeParams } from './params'; +import { Clipping } from '../../mol-theme/clipping'; export interface ComplexVisual<P extends StructureParams> extends Visual<Structure, P> { } @@ -217,6 +218,9 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge setTransparency(transparency: Transparency) { Visual.setTransparency(renderObject, transparency, lociApply, true); }, + setClipping(clipping: Clipping) { + Visual.setClipping(renderObject, clipping, lociApply, true); + }, destroy() { // TODO renderObject = undefined; diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index e9c63e97b44df9bb7bd35b8481185a15ca5a23ac..e85d479c947a71bcf0038f2e02aae4681fb5333f 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -23,6 +23,7 @@ import { Transparency } from '../../mol-theme/transparency'; import { Mat4, EPSILON } from '../../mol-math/linear-algebra'; import { Interval } from '../../mol-data/int'; import { StructureParams } from './params'; +import { Clipping } from '../../mol-theme/clipping'; export interface UnitsVisual<P extends StructureParams> extends Visual<StructureGroup, P> { } @@ -191,13 +192,14 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct } function setVisualState(visual: UnitsVisual<P>, group: Unit.SymmetryGroup, state: Partial<StructureRepresentationState>) { - const { visible, alphaFactor, pickable, overpaint, transparency, transform, unitTransforms } = state; + const { visible, alphaFactor, pickable, overpaint, transparency, clipping, transform, unitTransforms } = state; if (visible !== undefined) visual.setVisibility(visible); if (alphaFactor !== undefined) visual.setAlphaFactor(alphaFactor); if (pickable !== undefined) visual.setPickable(pickable); if (overpaint !== undefined) visual.setOverpaint(overpaint); if (transparency !== undefined) visual.setTransparency(transparency); + if (clipping !== undefined) visual.setClipping(clipping); if (transform !== undefined) visual.setTransform(transform); if (unitTransforms !== undefined) { if (unitTransforms) { @@ -210,7 +212,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct } function setState(state: Partial<StructureRepresentationState>) { - const { visible, alphaFactor, pickable, overpaint, transparency, transform, unitTransforms, syncManually, markerActions } = state; + const { visible, alphaFactor, pickable, overpaint, transparency, clipping, transform, unitTransforms, syncManually, markerActions } = state; const newState: Partial<StructureRepresentationState> = {}; if (visible !== _state.visible) newState.visible = visible; @@ -224,6 +226,11 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct if (transparency !== undefined && !Transparency.areEqual(transparency, _state.transparency)) { newState.transparency = transparency; } + if (clipping !== undefined && !Clipping.areEqual(clipping, _state.clipping)) { + if (_structure) { + newState.clipping = Clipping.remap(clipping, _structure); + } + } if (transform !== undefined && !Mat4.areEqual(transform, _state.transform, EPSILON)) { newState.transform = transform; } diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index 1b366407d53e5fe674dcf68071266cf943c705d0..8512f3c53763a8af82126e7b73d1ce7e73fd7771 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -36,6 +36,7 @@ import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh'; import { SizeValues } from '../../mol-gl/renderable/schema'; import { StructureParams, StructureMeshParams, StructureSpheresParams, StructurePointsParams, StructureLinesParams, StructureTextParams, StructureDirectVolumeParams, StructureTextureMeshParams } from './params'; +import { Clipping } from '../../mol-theme/clipping'; export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup } @@ -268,6 +269,9 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom setTransparency(transparency: Transparency) { Visual.setTransparency(renderObject, transparency, lociApply, true); }, + setClipping(clipping: Clipping) { + Visual.setClipping(renderObject, clipping, lociApply, true); + }, destroy() { // TODO renderObject = undefined; diff --git a/src/mol-repr/structure/visual/util/bond.ts b/src/mol-repr/structure/visual/util/bond.ts index 2feb4bbf55ab006e70237db959845d6a8636a0c7..4fa04997081090edb22450124b7cbdb6e96fd4fe 100644 --- a/src/mol-repr/structure/visual/util/bond.ts +++ b/src/mol-repr/structure/visual/util/bond.ts @@ -10,10 +10,11 @@ import { ParamDefinition as PD } from '../../../../mol-util/param-definition'; import { LocationIterator } from '../../../../mol-geo/util/location-iterator'; import { StructureGroup } from '../../units-visual'; import { LinkCylinderParams } from './link'; +import { ObjectKeys } from '../../../../mol-util/type-helpers'; export const BondCylinderParams = { ...LinkCylinderParams, - includeTypes: PD.MultiSelect(Object.keys(BondType.Names) as BondType.Names[], PD.objectToOptions(BondType.Names)), + includeTypes: PD.MultiSelect(ObjectKeys(BondType.Names), PD.objectToOptions(BondType.Names)), excludeTypes: PD.MultiSelect([] as BondType.Names[], PD.objectToOptions(BondType.Names)), }; export const DefaultBondCylinderProps = PD.getDefaultValues(BondCylinderParams); diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts index ca00f7ae27202cc7b000bad39b403853fd69b56b..37692bd76c43a2687201dba92e23e11923eb68d1 100644 --- a/src/mol-repr/visual.ts +++ b/src/mol-repr/visual.ts @@ -21,6 +21,8 @@ import { createOverpaint, clearOverpaint, applyOverpaintColor } from '../mol-geo import { Interval } from '../mol-data/int'; import { Transparency } from '../mol-theme/transparency'; import { createTransparency, clearTransparency, applyTransparencyValue } from '../mol-geo/geometry/transparency-data'; +import { Clipping } from '../mol-theme/clipping'; +import { createClipping, applyClippingGroups, clearClipping } from '../mol-geo/geometry/clipping-data'; export interface VisualContext { readonly runtime: RuntimeContext @@ -42,6 +44,7 @@ interface Visual<D, P extends PD.Params> { setTransform: (matrix?: Mat4, instanceMatrices?: Float32Array | null) => void setOverpaint: (overpaint: Overpaint) => void setTransparency: (transparency: Transparency) => void + setClipping: (clipping: Clipping) => void destroy: () => void } namespace Visual { @@ -128,6 +131,31 @@ namespace Visual { ValueCell.update(tTransparency, tTransparency.ref.value); } + export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) { + if (!renderObject) return; + + const { tClipping, uGroupCount, instanceCount } = renderObject.values; + const count = uGroupCount.ref.value * instanceCount.ref.value; + + // ensure texture has right size + createClipping(clipping.layers.length ? count : 0, renderObject.values); + const { array } = tClipping.ref.value; + + // clear if requested + if (clear) clearClipping(array, 0, count); + + for (let i = 0, il = clipping.layers.length; i < il; ++i) { + const { loci, groups } = clipping.layers[i]; + const apply = (interval: Interval) => { + const start = Interval.start(interval); + const end = Interval.end(interval); + return applyClippingGroups(array, start, end, groups); + }; + lociApply(loci, apply, false); + } + ValueCell.update(tClipping, tClipping.ref.value); + } + export function setTransform(renderObject: GraphicsRenderObject | undefined, transform?: Mat4, instanceTransforms?: Float32Array | null) { if (!renderObject || (!transform && !instanceTransforms)) return; diff --git a/src/mol-repr/volume/representation.ts b/src/mol-repr/volume/representation.ts index 7e97b98365fcfd42ef3bc2c14e1f711b65b1dc4c..014225cef17de50a694aff2b0955af9c45d647bc 100644 --- a/src/mol-repr/volume/representation.ts +++ b/src/mol-repr/volume/representation.ts @@ -29,6 +29,7 @@ import { BaseGeometry } from '../../mol-geo/geometry/base'; import { Subject } from 'rxjs'; import { Task } from '../../mol-task'; import { SizeValues } from '../../mol-gl/renderable/schema'; +import { Clipping } from '../../mol-theme/clipping'; export interface VolumeVisual<P extends VolumeParams> extends Visual<Volume, P> { } @@ -191,6 +192,9 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet setTransparency(transparency: Transparency) { return Visual.setTransparency(renderObject, transparency, lociApply, true); }, + setClipping(clipping: Clipping) { + return Visual.setClipping(renderObject, clipping, lociApply, true); + }, destroy() { // TODO renderObject = undefined; diff --git a/src/mol-theme/clipping.ts b/src/mol-theme/clipping.ts new file mode 100644 index 0000000000000000000000000000000000000000..41eb133c7bb8b5ab64c6aea781ea594bb934b1b9 --- /dev/null +++ b/src/mol-theme/clipping.ts @@ -0,0 +1,197 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Loci } from '../mol-model/loci'; +import { StructureElement, Structure } from '../mol-model/structure'; +import { Script } from '../mol-script/script'; +import BitFlags from '../mol-util/bit-flags'; + +export { Clipping }; + +type Clipping = { readonly layers: ReadonlyArray<Clipping.Layer> } + +function Clipping(layers: ReadonlyArray<Clipping.Layer>): Clipping { + return { layers }; +} + +namespace Clipping { + export type Layer = { readonly loci: StructureElement.Loci, readonly groups: Groups } + export const Empty: Clipping = { layers: [] }; + + export type Groups = BitFlags<Groups.Flag> + export namespace Groups { + export const is: (g: Groups, f: Flag) => boolean = BitFlags.has; + export const enum Flag { + None = 0x0, + One = 0x1, + Two = 0x2, + Three = 0x4, + Four = 0x8, + Five = 0x10, + Six = 0x20, + } + + export function create(flags: Flag): Groups { + return BitFlags.create(flags); + } + + export const Names = { + 'one': Flag.One, + 'two': Flag.Two, + 'three': Flag.Three, + 'four': Flag.Four, + 'five': Flag.Five, + 'six': Flag.Six, + }; + export type Names = keyof typeof Names + + export function isName(name: string): name is Names { + return name in Names; + } + + export function fromName(name: Names): Flag { + switch (name) { + case 'one': return Flag.One; + case 'two': return Flag.Two; + case 'three': return Flag.Three; + case 'four': return Flag.Four; + case 'five': return Flag.Five; + case 'six': return Flag.Six; + } + } + + export function fromNames(names: Names[]): Flag { + let f = Flag.None; + for (let i = 0, il = names.length; i < il; ++i) { + f |= fromName(names[i]); + } + return f; + } + + export function toNames(groups: Groups): Names[] { + let names: Names[] = []; + if (is(groups, Flag.One)) names.push('one'); + if (is(groups, Flag.Two)) names.push('two'); + if (is(groups, Flag.Three)) names.push('three'); + if (is(groups, Flag.Four)) names.push('four'); + if (is(groups, Flag.Five)) names.push('five'); + if (is(groups, Flag.Six)) names.push('six'); + return names; + } + } + + /** Clip object types */ + export const Type = { + none: 0, // to switch clipping off + plane: 1, + sphere: 2, + cube: 3, + cylinder: 4, + infiniteCone: 5, + }; + + export type Variant = 'instance' | 'pixel' + + export function areEqual(cA: Clipping, cB: Clipping) { + if (cA.layers.length === 0 && cB.layers.length === 0) return true; + if (cA.layers.length !== cB.layers.length) return false; + for (let i = 0, il = cA.layers.length; i < il; ++i) { + if (cA.layers[i].groups !== cB.layers[i].groups) return false; + if (!Loci.areEqual(cA.layers[i].loci, cB.layers[i].loci)) return false; + } + return true; + } + + export function isEmpty(clipping: Clipping) { + return clipping.layers.length === 0; + } + + export function remap(clipping: Clipping, structure: Structure) { + const layers: Clipping.Layer[] = []; + for (const layer of clipping.layers) { + let { loci, groups } = layer; + loci = StructureElement.Loci.remap(loci, structure); + if (!StructureElement.Loci.isEmpty(loci)) { + layers.push({ loci, groups }); + } + } + return { layers }; + } + + export function merge(clipping: Clipping): Clipping { + if (isEmpty(clipping)) return clipping; + const { structure } = clipping.layers[0].loci; + const map = new Map<Groups, StructureElement.Loci>(); + let shadowed = StructureElement.Loci.none(structure); + for (let i = 0, il = clipping.layers.length; i < il; ++i) { + let { loci, groups } = clipping.layers[il - i - 1]; // process from end + loci = StructureElement.Loci.subtract(loci, shadowed); + shadowed = StructureElement.Loci.union(loci, shadowed); + if (!StructureElement.Loci.isEmpty(loci)) { + if (map.has(groups)) { + loci = StructureElement.Loci.union(loci, map.get(groups)!); + } + map.set(groups, loci); + } + } + const layers: Clipping.Layer[] = []; + map.forEach((loci, groups) => { + layers.push({ loci, groups }); + }); + return { layers }; + } + + export function filter(clipping: Clipping, filter: Structure): Clipping { + if (isEmpty(clipping)) return clipping; + const { structure } = clipping.layers[0].loci; + const layers: Clipping.Layer[] = []; + for (const layer of clipping.layers) { + let { loci, groups } = layer; + // filter by first map to the `filter` structure and + // then map back to the original structure of the clipping loci + const filtered = StructureElement.Loci.remap(loci, filter); + loci = StructureElement.Loci.remap(filtered, structure); + if (!StructureElement.Loci.isEmpty(loci)) { + layers.push({ loci, groups }); + } + } + return { layers }; + } + + export type ScriptLayer = { script: Script, groups: Groups } + export function ofScript(scriptLayers: ScriptLayer[], structure: Structure): Clipping { + const layers: Clipping.Layer[] = []; + for (let i = 0, il = scriptLayers.length; i < il; ++i) { + const { script, groups } = scriptLayers[i]; + const loci = Script.toLoci(script, structure); + if (!StructureElement.Loci.isEmpty(loci)) { + layers.push({ loci, groups }); + } + } + return { layers }; + } + + export type BundleLayer = { bundle: StructureElement.Bundle, groups: Groups } + export function ofBundle(bundleLayers: BundleLayer[], structure: Structure): Clipping { + const layers: Clipping.Layer[] = []; + for (let i = 0, il = bundleLayers.length; i < il; ++i) { + const { bundle, groups } = bundleLayers[i]; + const loci = StructureElement.Bundle.toLoci(bundle, structure.root); + layers.push({ loci, groups }); + } + return { layers }; + } + + export function toBundle(clipping: Clipping) { + const layers: BundleLayer[] = []; + for (let i = 0, il = clipping.layers.length; i < il; ++i) { + let { loci, groups } = clipping.layers[i]; + const bundle = StructureElement.Bundle.fromLoci(loci); + layers.push({ bundle, groups }); + } + return { layers }; + } +} \ No newline at end of file diff --git a/src/mol-theme/transparency.ts b/src/mol-theme/transparency.ts index 4be6fb3889a07c6f2b76c71778f7a0bd544ec832..8d146faff3ad2d600251a7233ea7dacda82b61ac 100644 --- a/src/mol-theme/transparency.ts +++ b/src/mol-theme/transparency.ts @@ -27,11 +27,11 @@ namespace Transparency { return true; } - export function ofScript(script: Script, value: number, variant: Transparency.Variant, structure: Structure): Transparency { + export function ofScript(script: Script, value: number, variant: Variant, structure: Structure): Transparency { return { loci: Script.toLoci(script, structure), value, variant }; } - export function ofBundle(bundle: StructureElement.Bundle, value: number, variant: Transparency.Variant, structure: Structure): Transparency { + export function ofBundle(bundle: StructureElement.Bundle, value: number, variant: Variant, structure: Structure): Transparency { return { loci: StructureElement.Bundle.toLoci(bundle, structure), value, variant }; } } \ No newline at end of file diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 629f715d1798f2d052f1db421cf79f23d626da2f..d592eac5406adc739d41a25f5bb9e674ec40f475 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -423,7 +423,7 @@ export namespace ParamDefinition { return false; } - export function merge(params: Params, a: any, b: any): any { + export function merge<P extends Params>(params: P, a: any, b: any): Values<P> { if (a === undefined) return { ...b }; if (b === undefined) return { ...a }; diff --git a/src/tests/browser/marching-cubes.ts b/src/tests/browser/marching-cubes.ts index db1f2440c12ddae44f1f444072a9fe28ac47240a..ccd9c090bf50cb6dcc999de3264e5d7abc3c880e 100644 --- a/src/tests/browser/marching-cubes.ts +++ b/src/tests/browser/marching-cubes.ts @@ -6,7 +6,7 @@ import './index.html'; import { resizeCanvas } from '../../mol-canvas3d/util'; -import { Canvas3DParams } from '../../mol-canvas3d/canvas3d'; +import { Canvas3DParams, Canvas3D } from '../../mol-canvas3d/canvas3d'; import { ColorNames } from '../../mol-util/color/names'; import { PositionData, Box3D, Sphere3D } from '../../mol-math/geometry'; import { OrderedSet } from '../../mol-data/int'; @@ -31,10 +31,10 @@ const canvas = document.createElement('canvas'); parent.appendChild(canvas); resizeCanvas(canvas, parent); -const canvas3d = PD.merge(Canvas3DParams, PD.getDefaultValues(Canvas3DParams), { +const canvas3d = Canvas3D.fromCanvas(canvas, PD.merge(Canvas3DParams, PD.getDefaultValues(Canvas3DParams), { renderer: { backgroundColor: ColorNames.white }, camera: { mode: 'orthographic' } -}); +})); canvas3d.animate(); async function init() {