Browse Source

Merge pull request #310 from molstar/clipping

Per Object Clip Objects
Alexander Rose 3 years ago
parent
commit
ab4a24d8ab

+ 2 - 0
CHANGELOG.md

@@ -14,6 +14,8 @@ Note that since we don't clearly distinguish between a public and private interf
     - Combine pickObject/pickInstance/pickGroup shader variants into one
     - Combine markingDepth/markingMask shader variants into one
     - Correctly set shader define flags for overpaint, transparency, substance, clipping
+- [Breaking] Add per-object clip rendering properties (variant/objects)
+    - ``SimpleSettingsParams.clipping.variant/objects`` and ``RendererParams.clip`` were removed
 
 ## [v3.0.0-dev.6] - 2021-12-19
 

+ 1 - 1
src/mol-canvas3d/helper/bounding-sphere-helper.ts

@@ -161,5 +161,5 @@ const instanceMaterialId = getNextMaterialId();
 
 function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
     const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform);
-    return createRenderObject('mesh', values, { disposed: false, visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false, noClip: false }, materialId);
+    return createRenderObject('mesh', values, { disposed: false, visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false }, materialId);
 }

+ 0 - 1
src/mol-canvas3d/helper/camera-helper.ts

@@ -76,7 +76,6 @@ export class CameraHelper {
                     this.scene.clear();
                     const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
                     this.renderObject = createAxesRenderObject(params);
-                    this.renderObject.state.noClip = true;
                     this.scene.add(this.renderObject);
                     this.scene.commit();
 

+ 0 - 1
src/mol-canvas3d/helper/handle-helper.ts

@@ -73,7 +73,6 @@ export class HandleHelper {
                     this.scene.clear();
                     const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
                     this.renderObject = createHandleRenderObject(params);
-                    this.renderObject.state.noClip = true;
                     this.scene.add(this.renderObject);
                     this.scene.commit();
 

+ 20 - 1
src/mol-geo/geometry/base.ts

@@ -17,6 +17,7 @@ import { UniformColorTheme } from '../../mol-theme/color/uniform';
 import { UniformSizeTheme } from '../../mol-theme/size/uniform';
 import { smoothstep } from '../../mol-math/interpolate';
 import { Material } from '../../mol-util/material';
+import { Clip } from '../../mol-util/clip';
 
 export const VisualQualityInfo = {
     'custom': {},
@@ -81,6 +82,7 @@ export namespace BaseGeometry {
         alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
         quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
         material: Material.getParam(),
+        clip: PD.Group(Clip.Params),
     };
     export type Params = typeof Params
 
@@ -97,6 +99,7 @@ export namespace BaseGeometry {
     }
 
     export function createValues(props: PD.Values<Params>, counts: Counts) {
+        const clip = Clip.getClip(props.clip);
         return {
             alpha: ValueCell.create(props.alpha),
             uAlpha: ValueCell.create(props.alpha),
@@ -107,6 +110,14 @@ export namespace BaseGeometry {
             uRoughness: ValueCell.create(props.material.roughness),
             uBumpiness: ValueCell.create(props.material.bumpiness),
             dLightCount: ValueCell.create(1),
+
+            dClipObjectCount: ValueCell.create(clip.objects.count),
+            dClipVariant: ValueCell.create(clip.variant),
+            uClipObjectType: ValueCell.create(clip.objects.type),
+            uClipObjectInvert: ValueCell.create(clip.objects.invert),
+            uClipObjectPosition: ValueCell.create(clip.objects.position),
+            uClipObjectRotation: ValueCell.create(clip.objects.rotation),
+            uClipObjectScale: ValueCell.create(clip.objects.scale),
         };
     }
 
@@ -115,6 +126,15 @@ export namespace BaseGeometry {
         ValueCell.updateIfChanged(values.uMetalness, props.material.metalness);
         ValueCell.updateIfChanged(values.uRoughness, props.material.roughness);
         ValueCell.updateIfChanged(values.uBumpiness, props.material.bumpiness);
+
+        const clip = Clip.getClip(props.clip);
+        ValueCell.update(values.dClipObjectCount, clip.objects.count);
+        ValueCell.update(values.dClipVariant, clip.variant);
+        ValueCell.update(values.uClipObjectType, clip.objects.type);
+        ValueCell.update(values.uClipObjectInvert, clip.objects.invert);
+        ValueCell.update(values.uClipObjectPosition, clip.objects.position);
+        ValueCell.update(values.uClipObjectRotation, clip.objects.rotation);
+        ValueCell.update(values.uClipObjectScale, clip.objects.scale);
     }
 
     export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {
@@ -127,7 +147,6 @@ export namespace BaseGeometry {
             colorOnly: false,
             opaque,
             writeDepth: opaque,
-            noClip: false,
         };
     }
 

+ 3 - 13
src/mol-geo/geometry/clipping-data.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -10,18 +10,13 @@ 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;
-    }
+    array.fill(groups, start, end);
     return true;
 }
 
@@ -38,9 +33,6 @@ export function createClipping(count: number, clippingData?: ClippingData): Clip
         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),
@@ -53,12 +45,10 @@ export function createEmptyClipping(clippingData?: ClippingData): ClippingData {
     if (clippingData) {
         ValueCell.update(clippingData.tClipping, emptyClippingTexture);
         ValueCell.update(clippingData.uClippingTexDim, Vec2.create(1, 1));
+        ValueCell.updateIfChanged(clippingData.dClipping, false);
         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),

+ 0 - 1
src/mol-gl/renderable.ts

@@ -22,7 +22,6 @@ export type RenderableState = {
     colorOnly: boolean
     opaque: boolean
     writeDepth: boolean
-    noClip: boolean
 }
 
 export interface Renderable<T extends RenderableValues> {

+ 8 - 9
src/mol-gl/renderable/schema.ts

@@ -135,12 +135,6 @@ export const GlobalUniformSchema = {
 
     uTransparentBackground: UniformSpec('b'),
 
-    uClipObjectType: UniformSpec('i[]'),
-    uClipObjectInvert: UniformSpec('b[]'),
-    uClipObjectPosition: UniformSpec('v3[]'),
-    uClipObjectRotation: UniformSpec('v4[]'),
-    uClipObjectScale: UniformSpec('v3[]'),
-
     uLightDirection: UniformSpec('v3[]'),
     uLightColor: UniformSpec('v3[]'),
     uAmbientColor: UniformSpec('v3'),
@@ -256,9 +250,6 @@ export type SubstanceSchema = typeof SubstanceSchema
 export type SubstanceValues = Values<SubstanceSchema>
 
 export const ClippingSchema = {
-    dClipObjectCount: DefineSpec('number'),
-    dClipVariant: DefineSpec('string', ['instance', 'pixel']),
-
     uClippingTexDim: UniformSpec('v2'),
     tClipping: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
     dClipping: DefineSpec('boolean'),
@@ -276,6 +267,14 @@ export const BaseSchema = {
 
     dLightCount: DefineSpec('number'),
 
+    dClipObjectCount: DefineSpec('number'),
+    dClipVariant: DefineSpec('string', ['instance', 'pixel']),
+    uClipObjectType: UniformSpec('i[]'),
+    uClipObjectInvert: UniformSpec('b[]'),
+    uClipObjectPosition: UniformSpec('v3[]'),
+    uClipObjectRotation: UniformSpec('v4[]'),
+    uClipObjectScale: UniformSpec('v3[]'),
+
     aInstance: AttributeSpec('float32', 1, 1),
     /**
      * final per-instance transform calculated for instance `i` as

+ 1 - 83
src/mol-gl/renderer.ts

@@ -8,15 +8,13 @@ import { Viewport } from '../mol-canvas3d/camera/util';
 import { ICamera } from '../mol-canvas3d/camera';
 import { Scene } from './scene';
 import { WebGLContext } from './webgl/context';
-import { Mat4, Vec3, Vec4, Vec2, Quat } from '../mol-math/linear-algebra';
+import { Mat4, Vec3, Vec4, Vec2 } from '../mol-math/linear-algebra';
 import { GraphicsRenderable } from './renderable';
 import { Color } from '../mol-util/color';
 import { ValueCell, deepEqual } from '../mol-util';
 import { GlobalUniformValues } 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';
 import { degToRad } from '../mol-math/misc';
 import { createNullTexture, Texture, Textures } from './webgl/texture';
 import { arrayMapUpsert } from '../mol-util/array';
@@ -110,20 +108,6 @@ export const RendererParams = {
     }] }),
     ambientColor: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
     ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
-
-    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))),
-            invert: PD.Boolean(false),
-            position: PD.Vec3(Vec3()),
-            rotation: PD.Group({
-                axis: PD.Vec3(Vec3.create(1, 0, 0)),
-                angle: PD.Numeric(0, { min: -180, max: 180, step: 1 }, { description: 'Angle in Degrees' }),
-            }, { isExpanded: true }),
-            scale: PD.Vec3(Vec3.create(1, 1, 1)),
-        }, o => stringToWords(o.type))
-    })
 };
 export type RendererProps = PD.Values<typeof RendererParams>
 
@@ -150,47 +134,11 @@ function getLight(props: RendererProps['light'], light?: Light): Light {
     return { count: props.length, direction, color };
 }
 
-type Clip = {
-    variant: Clipping.Variant
-    objects: {
-        count: number
-        type: number[]
-        invert: boolean[]
-        position: number[]
-        rotation: number[]
-        scale: number[]
-    }
-}
-
-const tmpQuat = Quat();
-function getClip(props: RendererProps['clip'], clip?: Clip): Clip {
-    const { type, invert, position, rotation, scale } = clip?.objects || {
-        type: (new Array(5)).fill(1),
-        invert: (new Array(5)).fill(false),
-        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];
-        invert[i] = p.invert;
-        Vec3.toArray(p.position, position, i * 3);
-        Quat.toArray(Quat.setAxisAngle(tmpQuat, p.rotation.axis, degToRad(p.rotation.angle)), rotation, i * 4);
-        Vec3.toArray(p.scale, scale, i * 3);
-    }
-    return {
-        variant: props.variant,
-        objects: { count: props.objects.length, type, invert, position, rotation, scale }
-    };
-}
-
 namespace Renderer {
     export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
         const { gl, state, stats, extensions: { fragDepth } } = ctx;
         const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
         const light = getLight(p.light);
-        const clip = getClip(p.clip);
 
         const viewport = Viewport();
         const drawingBufferSize = Vec2.create(gl.drawingBufferWidth, gl.drawingBufferHeight);
@@ -250,12 +198,6 @@ namespace Renderer {
 
             uTransparentBackground: ValueCell.create(false),
 
-            uClipObjectType: ValueCell.create(clip.objects.type),
-            uClipObjectInvert: ValueCell.create(clip.objects.invert),
-            uClipObjectPosition: ValueCell.create(clip.objects.position),
-            uClipObjectRotation: ValueCell.create(clip.objects.rotation),
-            uClipObjectScale: ValueCell.create(clip.objects.scale),
-
             uLightDirection: ValueCell.create(light.direction),
             uLightColor: ValueCell.create(light.color),
             uAmbientColor: ValueCell.create(ambientColor),
@@ -284,21 +226,6 @@ namespace Renderer {
             }
 
             let definesNeedUpdate = false;
-            if (r.state.noClip) {
-                if (r.values.dClipObjectCount.ref.value !== 0) {
-                    ValueCell.update(r.values.dClipObjectCount, 0);
-                    definesNeedUpdate = true;
-                }
-            } else {
-                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 (r.values.dLightCount.ref.value !== light.count) {
                 ValueCell.update(r.values.dLightCount, light.count);
                 definesNeedUpdate = true;
@@ -704,15 +631,6 @@ namespace Renderer {
                     Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
                     ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
                 }
-
-                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);

+ 20 - 1
src/mol-math/linear-algebra/3d/quat.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -346,6 +346,25 @@ namespace Quat {
         return out;
     }
 
+    /**
+     * Returns whether or not the quaternions have exactly the same elements in the same position (when compared with ===)
+     */
+    export function exactEquals(a: Quat, b: Quat) {
+        return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
+    }
+
+    /**
+     * Returns whether or not the quaternions have approximately the same elements in the same position.
+     */
+    export function equals(a: Quat, b: Quat) {
+        const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3];
+        const b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
+        return (Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
+                Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) &&
+                Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
+                Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)));
+    }
+
     export function add(out: Quat, a: Quat, b: Quat) {
         out[0] = a[0] + b[0];
         out[1] = a[1] + b[1];

+ 1 - 1
src/mol-plugin-state/helpers/structure-clipping.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>

+ 12 - 7
src/mol-plugin-state/manager/structure/component.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { VisualQualityOptions } from '../../../mol-geo/geometry/base';
@@ -32,6 +33,7 @@ import { setStructureTransparency } from '../../helpers/structure-transparency';
 import { StructureFocusRepresentation } from '../../../mol-plugin/behavior/dynamic/selection/structure-focus-representation';
 import { setStructureSubstance } from '../../helpers/structure-substance';
 import { Material } from '../../../mol-util/material';
+import { Clip } from '../../../mol-util/clip';
 
 export { StructureComponentManager };
 
@@ -70,23 +72,25 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
             await this.plugin.state.updateBehavior(StructureFocusRepresentation, p => {
                 p.ignoreHydrogens = !options.showHydrogens;
                 p.material = options.materialStyle;
+                p.clip = options.clipObjects;
             });
             if (interactionChanged) await this.updateInterationProps();
         });
     }
 
     private updateReprParams(update: StateBuilder.Root, component: StructureComponentRef) {
-        const { showHydrogens, visualQuality: quality, materialStyle: material } = this.state.options;
+        const { showHydrogens, visualQuality: quality, materialStyle: material, clipObjects: clip } = this.state.options;
         const ignoreHydrogens = !showHydrogens;
         for (const r of component.representations) {
             if (r.cell.transform.transformer !== StructureRepresentation3D) continue;
 
             const params = r.cell.transform.params as StateTransformer.Params<StructureRepresentation3D>;
-            if (!!params.type.params.ignoreHydrogens !== ignoreHydrogens || params.type.params.quality !== quality || !shallowEqual(params.type.params.material, material)) {
+            if (!!params.type.params.ignoreHydrogens !== ignoreHydrogens || params.type.params.quality !== quality || !shallowEqual(params.type.params.material, material) || !PD.areEqual(Clip.Params, params.type.params.clip, clip)) {
                 update.to(r.cell).update(old => {
                     old.type.params.ignoreHydrogens = ignoreHydrogens;
                     old.type.params.quality = quality;
                     old.type.params.material = material;
+                    old.type.params.clip = clip;
                 });
             }
         }
@@ -305,9 +309,9 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
     addRepresentation(components: ReadonlyArray<StructureComponentRef>, type: string) {
         if (components.length === 0) return;
 
-        const { showHydrogens, visualQuality: quality, materialStyle: material } = this.state.options;
+        const { showHydrogens, visualQuality: quality, materialStyle: material, clipObjects: clip } = this.state.options;
         const ignoreHydrogens = !showHydrogens;
-        const typeParams = { ignoreHydrogens, quality, material };
+        const typeParams = { ignoreHydrogens, quality, material, clip };
 
         return this.plugin.dataTransaction(async () => {
             for (const component of components) {
@@ -342,9 +346,9 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
             const xs = structures || this.currentStructures;
             if (xs.length === 0) return;
 
-            const { showHydrogens, visualQuality: quality, materialStyle: material } = this.state.options;
+            const { showHydrogens, visualQuality: quality, materialStyle: material, clipObjects: clip } = this.state.options;
             const ignoreHydrogens = !showHydrogens;
-            const typeParams = { ignoreHydrogens, quality, material };
+            const typeParams = { ignoreHydrogens, quality, material, clip };
 
             const componentKey = UUID.create22();
             for (const s of xs) {
@@ -455,6 +459,7 @@ namespace StructureComponentManager {
         showHydrogens: PD.Boolean(true, { description: 'Toggle display of hydrogen atoms in representations' }),
         visualQuality: PD.Select('auto', VisualQualityOptions, { description: 'Control the visual/rendering quality of representations' }),
         materialStyle: Material.getParam(),
+        clipObjects: PD.Group(Clip.Params),
         interactions: PD.Group(InteractionsProvider.defaultParams, { label: 'Non-covalent Interactions' }),
     };
     export type Options = PD.Values<typeof OptionsParams>

+ 0 - 6
src/mol-plugin-ui/viewport/simple-settings.tsx

@@ -62,7 +62,6 @@ const SimpleSettingsParams = {
     }, { isFlat: true }),
     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)),
 };
@@ -110,7 +109,6 @@ const SimpleSettingsMapping = ParamMapping({
             },
             clipping: {
                 ...canvas.cameraClipping,
-                ...canvas.renderer.clip
             }
         };
     },
@@ -128,10 +126,6 @@ const SimpleSettingsMapping = ParamMapping({
             radius: s.clipping.radius,
             far: s.clipping.far,
         };
-        canvas.renderer.clip = {
-            variant: s.clipping.variant,
-            objects: s.clipping.objects,
-        };
 
         props.layout = s.layout;
     },

+ 3 - 1
src/mol-plugin/behavior/dynamic/selection/structure-focus-representation.ts

@@ -19,6 +19,7 @@ import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
 import { PluginCommands } from '../../../commands';
 import { PluginContext } from '../../../context';
 import { Material } from '../../../../mol-util/material';
+import { Clip } from '../../../../mol-util/clip';
 
 const StructureFocusRepresentationParams = (plugin: PluginContext) => {
     const reprParams = StateTransforms.Representation.StructureRepresentation3D.definition.params!(void 0, plugin) as PD.Params;
@@ -44,6 +45,7 @@ const StructureFocusRepresentationParams = (plugin: PluginContext) => {
         excludeTargetFromSurroundings: PD.Boolean(false, { label: 'Exclude Target', description: 'Exclude the focus "target" from the surroudings component.' }),
         ignoreHydrogens: PD.Boolean(false),
         material: Material.getParam(),
+        clip: PD.Group(Clip.Params),
     };
 };
 
@@ -69,7 +71,7 @@ class StructureFocusRepresentationBehavior extends PluginBehavior.WithSubscriber
             ...this.params.targetParams,
             type: {
                 name: reprParams.type.name,
-                params: { ...reprParams.type.params, ignoreHydrogens: this.params.ignoreHydrogens, material: this.params.material }
+                params: { ...reprParams.type.params, ignoreHydrogens: this.params.ignoreHydrogens, material: this.params.material, clip: this.params.clip }
             }
         };
     }

+ 1 - 0
src/mol-plugin/spec.ts

@@ -101,6 +101,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
         PluginSpec.Action(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D),
         PluginSpec.Action(StateTransforms.Representation.OverpaintStructureRepresentation3DFromScript),
         PluginSpec.Action(StateTransforms.Representation.TransparencyStructureRepresentation3DFromScript),
+        PluginSpec.Action(StateTransforms.Representation.ClippingStructureRepresentation3DFromScript),
         PluginSpec.Action(StateTransforms.Representation.SubstanceStructureRepresentation3DFromScript),
 
         PluginSpec.Action(AssignColorVolume),

+ 2 - 1
src/mol-repr/visual.ts

@@ -308,9 +308,10 @@ namespace Visual {
 
         const { tClipping, dClipping, uGroupCount, instanceCount } = renderObject.values;
         const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { layers } = clipping;
 
         // ensure texture has right size
-        createClipping(clipping.layers.length ? count : 0, renderObject.values);
+        createClipping(layers.length ? count : 0, renderObject.values);
         const { array } = tClipping.ref.value;
 
         // clear if requested

+ 10 - 17
src/mol-theme/clipping.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -11,9 +11,11 @@ import { BitFlags } from '../mol-util/bit-flags';
 
 export { Clipping };
 
-type Clipping = { readonly layers: ReadonlyArray<Clipping.Layer> }
+type Clipping = {
+    readonly layers: ReadonlyArray<Clipping.Layer>
+}
 
-function Clipping(layers: ReadonlyArray<Clipping.Layer>): Clipping {
+function Clipping(layers: Clipping['layers']): Clipping {
     return { layers };
 }
 
@@ -83,20 +85,7 @@ namespace Clipping {
         }
     }
 
-    /** 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;
@@ -105,11 +94,13 @@ namespace Clipping {
         return true;
     }
 
+    /** Check if layers empty */
     export function isEmpty(clipping: Clipping) {
         return clipping.layers.length === 0;
     }
 
-    export function remap(clipping: Clipping, structure: Structure) {
+    /** Remap layers */
+    export function remap(clipping: Clipping, structure: Structure): Clipping {
         const layers: Clipping.Layer[] = [];
         for (const layer of clipping.layers) {
             let { loci, groups } = layer;
@@ -121,6 +112,7 @@ namespace Clipping {
         return { layers };
     }
 
+    /** Merge layers */
     export function merge(clipping: Clipping): Clipping {
         if (isEmpty(clipping)) return clipping;
         const { structure } = clipping.layers[0].loci;
@@ -144,6 +136,7 @@ namespace Clipping {
         return { layers };
     }
 
+    /** Filter layers */
     export function filter(clipping: Clipping, filter: Structure): Clipping {
         if (isEmpty(clipping)) return clipping;
         const { structure } = clipping.layers[0].loci;

+ 114 - 0
src/mol-util/clip.ts

@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Quat, Vec3 } from '../mol-math/linear-algebra';
+import { degToRad } from '../mol-math/misc';
+import { ParamDefinition as PD } from './param-definition';
+import { stringToWords } from './string';
+
+export interface Clip {
+    variant: Clip.Variant,
+    objects: Clip.Objects
+}
+
+export function Clip() {
+
+}
+
+export namespace Clip {
+    /** 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 type Objects = {
+        count: number
+        type: number[]
+        invert: boolean[]
+        position: number[]
+        rotation: number[]
+        scale: number[]
+    }
+
+    export const Params = {
+        variant: PD.Select('pixel', PD.arrayToOptions<Variant>(['instance', 'pixel'])),
+        objects: PD.ObjectList({
+            type: PD.Select('plane', PD.objectToOptions(Type, t => stringToWords(t))),
+            invert: PD.Boolean(false),
+            position: PD.Vec3(Vec3()),
+            rotation: PD.Group({
+                axis: PD.Vec3(Vec3.create(1, 0, 0)),
+                angle: PD.Numeric(0, { min: -180, max: 180, step: 1 }, { description: 'Angle in Degrees' }),
+            }, { isExpanded: true }),
+            scale: PD.Vec3(Vec3.create(1, 1, 1)),
+        }, o => stringToWords(o.type))
+    };
+    export type Params = typeof Params
+    export type Props = PD.Values<Params>
+
+    function createClipObjects() {
+        return {
+            count: 0,
+            type: (new Array(5)).fill(1),
+            invert: (new Array(5)).fill(false),
+            position: (new Array(5 * 3)).fill(0),
+            rotation: (new Array(5 * 4)).fill(0),
+            scale: (new Array(5 * 3)).fill(1),
+        };
+    }
+
+    const qA = Quat();
+    const qB = Quat();
+    const vA = Vec3();
+    const vB = Vec3();
+
+    export function getClip(props: Props, clip?: Clip): Clip {
+        const { type, invert, position, rotation, scale } = clip?.objects || createClipObjects();
+        for (let i = 0, il = props.objects.length; i < il; ++i) {
+            const p = props.objects[i];
+            type[i] = Type[p.type];
+            invert[i] = p.invert;
+            Vec3.toArray(p.position, position, i * 3);
+            Quat.toArray(Quat.setAxisAngle(qA, p.rotation.axis, degToRad(p.rotation.angle)), rotation, i * 4);
+            Vec3.toArray(p.scale, scale, i * 3);
+        }
+        return {
+            variant: props.variant,
+            objects: { count: props.objects.length, type, invert, position, rotation, scale }
+        };
+    }
+
+    export function areEqual(cA: Clip, cB: Clip) {
+        if (cA.variant !== cB.variant) return false;
+        if (cA.objects.count !== cB.objects.count) return false;
+
+        const oA = cA.objects, oB = cB.objects;
+        for (let i = 0, il = oA.count; i < il; ++i) {
+            if (oA.invert[i] !== oB.invert[i]) return false;
+            if (oA.type[i] !== oB.type[i]) return false;
+
+            Vec3.fromArray(vA, oA.position, i * 3);
+            Vec3.fromArray(vB, oB.position, i * 3);
+            if (!Vec3.equals(vA, vB)) return false;
+
+            Vec3.fromArray(vA, oA.scale, i * 3);
+            Vec3.fromArray(vB, oB.scale, i * 3);
+            if (!Vec3.equals(vA, vB)) return false;
+
+            Quat.fromArray(qA, oA.rotation, i * 4);
+            Quat.fromArray(qB, oB.rotation, i * 4);
+            if (!Quat.equals(qA, qB)) return false;
+        }
+        return true;
+    }
+}