ソースを参照

wip: prefer to pick atoms close to ends over bonds

Alexander Rose 3 年 前
コミット
d4bb1a6e93

+ 5 - 2
src/mol-canvas3d/canvas3d.ts

@@ -23,7 +23,7 @@ import { Camera } from './camera';
 import { ParamDefinition as PD } from '../mol-util/param-definition';
 import { DebugHelperParams } from './helper/bounding-sphere-helper';
 import { SetUtils } from '../mol-util/set';
-import { Canvas3dInteractionHelper } from './helper/interaction-events';
+import { Canvas3dInteractionHelper, Canvas3dInteractionHelperParams } from './helper/interaction-events';
 import { PostprocessingParams } from './passes/postprocessing';
 import { MultiSampleHelper, MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
 import { PickData } from './passes/pick';
@@ -84,6 +84,7 @@ export const Canvas3DParams = {
     marking: PD.Group(MarkingParams),
     renderer: PD.Group(RendererParams),
     trackball: PD.Group(TrackballControlsParams),
+    interaction: PD.Group(Canvas3dInteractionHelperParams),
     debug: PD.Group(DebugHelperParams),
     handle: PD.Group(HandleHelperParams),
 };
@@ -306,7 +307,7 @@ namespace Canvas3D {
         const helper = new Helper(webgl, scene, p);
 
         const pickHelper = new PickHelper(webgl, renderer, scene, helper, passes.pick, { x, y, width, height });
-        const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera);
+        const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera, p.interaction);
         const multiSampleHelper = new MultiSampleHelper(passes.multiSample);
 
         let cameraResetRequested = false;
@@ -642,6 +643,7 @@ namespace Canvas3D {
                 multiSample: { ...p.multiSample },
                 renderer: { ...renderer.props },
                 trackball: { ...controls.props },
+                interaction: { ...interactionHelper.props },
                 debug: { ...helper.debug.props },
                 handle: { ...helper.handle.props },
             };
@@ -778,6 +780,7 @@ namespace Canvas3D {
                 if (props.multiSample) Object.assign(p.multiSample, props.multiSample);
                 if (props.renderer) renderer.setProps(props.renderer);
                 if (props.trackball) controls.setProps(props.trackball);
+                if (props.interaction) interactionHelper.setProps(props.interaction);
                 if (props.debug) helper.debug.setProps(props.debug);
                 if (props.handle) helper.handle.setProps(props.handle);
 

+ 43 - 5
src/mol-canvas3d/helper/interaction-events.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -11,6 +11,9 @@ import { InputObserver, ModifiersKeys, ButtonsType } from '../../mol-util/input/
 import { RxEventHelper } from '../../mol-util/rx-event-helper';
 import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
 import { Camera } from '../camera';
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import { Bond, StructureElement } from '../../mol-model/structure';
+import { OrderedSet } from '../../mol-data/int';
 
 type Canvas3D = import('../canvas3d').Canvas3D
 type HoverEvent = import('../canvas3d').Canvas3D.HoverEvent
@@ -19,6 +22,15 @@ type ClickEvent = import('../canvas3d').Canvas3D.ClickEvent
 
 const enum InputEvent { Move, Click, Drag }
 
+const pA = Vec3();
+
+export const Canvas3dInteractionHelperParams = {
+    maxFps: PD.Numeric(30, { min: 10, max: 60, step: 10 }),
+    preferAtomDistanceFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }),
+};
+export type Canvas3dInteractionHelperParams = typeof Canvas3dInteractionHelperParams
+export type Canvas3dInteractionHelperProps = PD.Values<Canvas3dInteractionHelperParams>
+
 export class Canvas3dInteractionHelper {
     private ev = RxEventHelper.create();
 
@@ -48,6 +60,12 @@ export class Canvas3dInteractionHelper {
     private button: ButtonsType.Flag = ButtonsType.create(0);
     private modifiers: ModifiersKeys = ModifiersKeys.None;
 
+    readonly props: Canvas3dInteractionHelperProps;
+
+    setProps(props: Partial<Canvas3dInteractionHelperProps>) {
+        Object.assign(this.props, props);
+    }
+
     private identify(e: InputEvent, t: number) {
         const xyChanged = this.startX !== this.endX || this.startY !== this.endY;
 
@@ -70,7 +88,7 @@ export class Canvas3dInteractionHelper {
         }
 
         if (e === InputEvent.Click) {
-            const loci = this.getLoci(this.id);
+            const loci = this.getLoci(this.id, this.position);
             this.events.click.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, page: Vec2.create(this.endX, this.endY), position: this.position });
             this.prevLoci = loci;
             return;
@@ -78,13 +96,13 @@ export class Canvas3dInteractionHelper {
 
         if (!this.inside || this.currentIdentifyT !== t || !xyChanged || this.outsideViewport(this.endX, this.endY)) return;
 
-        const loci = this.getLoci(this.id);
+        const loci = this.getLoci(this.id, this.position);
         this.events.hover.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, page: Vec2.create(this.endX, this.endY), position: this.position });
         this.prevLoci = loci;
     }
 
     tick(t: number) {
-        if (this.inside && t - this.prevT > 1000 / this.maxFps) {
+        if (this.inside && t - this.prevT > 1000 / this.props.maxFps) {
             this.prevT = t;
             this.currentIdentifyT = t;
             this.identify(this.isInteracting ? InputEvent.Drag : InputEvent.Move, t);
@@ -144,11 +162,31 @@ export class Canvas3dInteractionHelper {
         );
     }
 
+    private getLoci(pickingId: PickingId | undefined, position: Vec3 | undefined) {
+        const { repr, loci } = this.lociGetter(pickingId);
+        if (position && repr && Bond.isLoci(loci) && loci.bonds.length === 2) {
+            const { aUnit, aIndex } = loci.bonds[0];
+            aUnit.conformation.position(aUnit.elements[aIndex], pA);
+            // TODO: project position onto camera plane at pA
+            let size = repr.theme.size.size(loci.bonds[0]) * (repr.props.sizeFactor ?? 1);
+            if (repr.props.lineSizeAttenuation === false) {
+                // TODO: properly scale?
+                size /= this.camera.pixelRatio * ((this.camera.viewport.height / 2.0) / -position[2]) * 5.0;
+            }
+            if (Vec3.distance(position, pA) < size * this.props.preferAtomDistanceFactor) {
+                return { repr, loci: StructureElement.Loci(loci.structure, [{ unit: aUnit, indices: OrderedSet.ofSingleton(aIndex) }]) };
+            }
+        }
+        return { repr, loci };
+    }
+
     dispose() {
         this.ev.dispose();
     }
 
-    constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], private input: InputObserver, private camera: Camera, private maxFps: number = 30) {
+    constructor(private canvasIdentify: Canvas3D['identify'], private lociGetter: Canvas3D['getLoci'], private input: InputObserver, private camera: Camera, props: Partial<Canvas3dInteractionHelperProps> = {}) {
+        this.props = { ...PD.getDefaultValues(Canvas3dInteractionHelperParams), ...props };
+
         input.drag.subscribe(({ x, y, buttons, button, modifiers }) => {
             this.isInteracting = true;
             // console.log('drag');