View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.ui;
39  
40  import ffx.potential.MolecularAssembly;
41  import ffx.potential.bonded.Atom;
42  import ffx.ui.GraphicsCanvas.LeftButtonMode;
43  import ffx.ui.behaviors.GlobalBehavior;
44  import ffx.ui.behaviors.MouseRotate;
45  import ffx.ui.behaviors.MouseTranslate;
46  import ffx.ui.behaviors.MouseZoom;
47  import java.awt.AWTEvent;
48  import java.awt.event.MouseEvent;
49  import java.util.Iterator;
50  import org.jogamp.java3d.Behavior;
51  import org.jogamp.java3d.Bounds;
52  import org.jogamp.java3d.BranchGroup;
53  import org.jogamp.java3d.Node;
54  import org.jogamp.java3d.SceneGraphPath;
55  import org.jogamp.java3d.Shape3D;
56  import org.jogamp.java3d.TransformGroup;
57  import org.jogamp.java3d.WakeupCriterion;
58  import org.jogamp.java3d.WakeupOnAWTEvent;
59  import org.jogamp.java3d.WakeupOnBehaviorPost;
60  import org.jogamp.java3d.WakeupOr;
61  import org.jogamp.java3d.utils.picking.PickCanvas;
62  import org.jogamp.java3d.utils.picking.PickIntersection;
63  import org.jogamp.java3d.utils.picking.PickResult;
64  import org.jogamp.java3d.utils.universe.SimpleUniverse;
65  import org.jogamp.vecmath.Point3d;
66  
67  /**
68   * The GraphicsEvents class listens for mouse events over the Java3D GraphicsCanvas, dispatching
69   * work to more specialized System Rotation and Translation Behaviors or to the GlobalOrbitBehavior.
70   *
71   * @author Michael J. Schnieders
72   */
73  public class GraphicsEvents extends Behavior {
74  
75    // Behavior Post IDs
76    /** Constant <code>ROTATEPOST=1</code> */
77    private static int ROTATEPOST = 1;
78    /** Constant <code>TRANSLATEPOST=2</code> */
79    private static int TRANSLATEPOST = 2;
80    /** Constant <code>ZOOMPOST=3</code> */
81    private static int ZOOMPOST = 3;
82    /** Constant <code>BEHAVIORDONEPOST=4</code> */
83    private static int BEHAVIORDONEPOST = 4;
84  
85    // GUI Panels
86    private MainPanel mainPanel;
87    private GraphicsCanvas graphicsCanvas;
88    // Wake up conditions
89    private WakeupOr mouseCriterion;
90    private WakeupOr postCriterion;
91    // Mouse/Pick state upon wake up event
92    private boolean buttonPress;
93    private boolean leftButton;
94    private boolean rightButton;
95    private boolean middleButton;
96    private PickCanvas pickCanvas;
97    private PickResult pickResult;
98    private Atom atom;
99    private boolean axisSelected;
100   // Behaviors
101   private MouseRotate systemRotate;
102   private MouseTranslate systemTranslate;
103   private MouseZoom globalZoom;
104   private GlobalBehavior viewOrbitBehavior;
105 
106   /**
107    * Constructor for GraphicsEvents.
108    *
109    * @param mainPanel a {@link ffx.ui.MainPanel} object.
110    * @param graphicsCanvas a {@link ffx.ui.GraphicsCanvas} object.
111    * @param graphicsAxis a {@link ffx.ui.GraphicsAxis} object.
112    * @param simpleUniverse a {@link org.jogamp.java3d.utils.universe.SimpleUniverse} object.
113    * @param bounds a {@link org.jogamp.java3d.Bounds} object.
114    * @param root a {@link org.jogamp.java3d.BranchGroup} object.
115    * @param transformGroup a {@link org.jogamp.java3d.TransformGroup} object.
116    */
117   public GraphicsEvents(
118       MainPanel mainPanel,
119       GraphicsCanvas graphicsCanvas,
120       GraphicsAxis graphicsAxis,
121       SimpleUniverse simpleUniverse,
122       Bounds bounds,
123       BranchGroup root,
124       TransformGroup transformGroup) {
125     this.mainPanel = mainPanel;
126     this.graphicsCanvas = graphicsCanvas;
127     // Scenegraph Nodes
128     TransformGroup viewTransformGroup =
129         simpleUniverse.getViewingPlatform().getViewPlatformTransform();
130     setSchedulingBounds(bounds);
131     // Initialize the System Rotate Behavior
132     systemRotate =
133         new MouseRotate(
134             MouseRotate.MANUAL_WAKEUP, viewTransformGroup, this, ROTATEPOST, BEHAVIORDONEPOST);
135     systemRotate.setFactor(0.025);
136     systemRotate.setSchedulingBounds(bounds);
137     root.addChild(systemRotate);
138     // Initialize the System Translate Behavior
139     systemTranslate =
140         new MouseTranslate(
141             MouseTranslate.MANUAL_WAKEUP,
142             viewTransformGroup,
143             this,
144             TRANSLATEPOST,
145             BEHAVIORDONEPOST);
146     systemTranslate.setFactor(0.5);
147     systemTranslate.setSchedulingBounds(bounds);
148     root.addChild(systemTranslate);
149     // Initialize the globalZoom Behavior
150     globalZoom =
151         new MouseZoom(
152             MouseZoom.MANUAL_WAKEUP, viewTransformGroup, this, ZOOMPOST, BEHAVIORDONEPOST);
153     globalZoom.setFactor(0.0005);
154     globalZoom.setSchedulingBounds(bounds);
155     globalZoom.setTransformGroup(transformGroup);
156     root.addChild(globalZoom);
157     // Initialize the viewOrbitBehavior
158     viewOrbitBehavior = new GlobalBehavior(this.graphicsCanvas);
159     viewOrbitBehavior.setUpCallback(graphicsAxis);
160     viewOrbitBehavior.setSchedulingBounds(bounds);
161     simpleUniverse.getViewingPlatform().setViewPlatformBehavior(viewOrbitBehavior);
162     // Initialize the PickCanvas
163     pickCanvas = new PickCanvas(this.graphicsCanvas, simpleUniverse.getLocale());
164     pickCanvas.setMode(PickCanvas.GEOMETRY);
165     pickCanvas.setTolerance(20.0f);
166   }
167 
168   /** initialize */
169   public void initialize() {
170     WakeupCriterion[] behaviorPost = new WakeupCriterion[3];
171     behaviorPost[0] = new WakeupOnBehaviorPost(systemRotate, BEHAVIORDONEPOST);
172     behaviorPost[1] = new WakeupOnBehaviorPost(systemTranslate, BEHAVIORDONEPOST);
173     behaviorPost[2] = new WakeupOnBehaviorPost(globalZoom, BEHAVIORDONEPOST);
174     postCriterion = new WakeupOr(behaviorPost);
175     WakeupCriterion[] awtCriterion = new WakeupCriterion[1];
176     awtCriterion[0] = new WakeupOnAWTEvent(java.awt.AWTEvent.MOUSE_EVENT_MASK);
177     mouseCriterion = new WakeupOr(awtCriterion);
178     wakeupOn(mouseCriterion);
179   }
180 
181   /**
182    * Most of the logic for mouse interaction with the Scenegraph is here.
183    *
184    * <p>{@inheritDoc}
185    */
186   @Override
187   public void processStimulus(Iterator<WakeupCriterion> criteria) {
188     viewOrbitBehavior.setEnable(false);
189     while (criteria.hasNext()) {
190       WakeupCriterion wakeup = criteria.next();
191       if (wakeup instanceof WakeupOnAWTEvent) {
192         AWTEvent[] awtEvents = ((WakeupOnAWTEvent) wakeup).getAWTEvent();
193         if (awtEvents == null) {
194           continue;
195         }
196         for (AWTEvent awtEvent : awtEvents) {
197           MouseEvent mouseEvent;
198           if (awtEvent instanceof MouseEvent) {
199             mouseEvent = (MouseEvent) awtEvent;
200             processMouseEvent(mouseEvent);
201           } else {
202             continue;
203           }
204           if (!axisSelected) {
205             // Wake Up System Translate Behavior
206             if (rightButton && buttonPress) {
207               systemTranslate.setMouseButton(MouseEvent.BUTTON3_DOWN_MASK);
208               if (systemTranslate()) {
209                 wakeupOn(postCriterion);
210                 return;
211               }
212             }
213             // Wake Up Left Button Mode
214             if (leftButton && buttonPress) {
215               LeftButtonMode leftButtonMode = graphicsCanvas.getLeftButtonMode();
216               switch (leftButtonMode) {
217                 case ROTATE:
218                   if (systemRotate()) {
219                     wakeupOn(postCriterion);
220                     return;
221                   }
222                   break;
223                 case TRANSLATE:
224                   systemTranslate.setMouseButton(MouseEvent.BUTTON1_DOWN_MASK);
225                   if (systemTranslate()) {
226                     wakeupOn(postCriterion);
227                     return;
228                   }
229                   break;
230                 case ZOOM:
231                   globalZoom.setMouseButton(MouseEvent.BUTTON1_DOWN_MASK);
232                   if (globalZoom()) {
233                     wakeupOn(postCriterion);
234                     return;
235                   }
236               }
237             }
238             // Wake up Global Zoom Behavior
239             if (middleButton && buttonPress) {
240               globalZoom.setMouseButton(MouseEvent.BUTTON2_DOWN_MASK);
241               if (globalZoom()) {
242                 wakeupOn(postCriterion);
243                 return;
244               }
245             }
246           } else {
247             viewOrbitBehavior.setEnable(true);
248             wakeupOn(mouseCriterion);
249             return;
250           }
251         }
252       }
253     }
254     wakeupOn(mouseCriterion);
255   }
256 
257   /**
258    * setGlobalCenter
259    *
260    * @param d an array of double.
261    */
262   public void setGlobalCenter(double[] d) {
263     Point3d point = new Point3d(d);
264     viewOrbitBehavior.setRotationCenter(point);
265   }
266 
267   /**
268    * centerView
269    *
270    * @param resetRotation a boolean.
271    * @param resetTranslation a boolean.
272    * @param resetZoom a boolean.
273    */
274   void centerView(boolean resetRotation, boolean resetTranslation, boolean resetZoom) {
275     viewOrbitBehavior.centerView(resetRotation, resetTranslation);
276   }
277 
278   private boolean globalZoom() {
279     postId(ZOOMPOST);
280     return true;
281   }
282 
283   /**
284    * processMouseEvent
285    *
286    * @param evt a {@link java.awt.event.MouseEvent} object.
287    */
288   private void processMouseEvent(MouseEvent evt) {
289     buttonPress = false;
290     leftButton = false;
291     middleButton = false;
292     rightButton = false;
293     int mod = evt.getModifiersEx();
294     if (evt.getID() == MouseEvent.MOUSE_PRESSED) {
295       buttonPress = true;
296     }
297     // Left Button
298     if ((mod & MouseEvent.BUTTON1_DOWN_MASK) == MouseEvent.BUTTON1_DOWN_MASK) {
299       leftButton = true;
300     }
301     // Middle Button
302     if ((mod & MouseEvent.BUTTON2_DOWN_MASK) == MouseEvent.BUTTON2_DOWN_MASK) {
303       middleButton = true;
304     }
305     // Alternatively, map "alt + button1" to the middle button
306     if ((mod & MouseEvent.ALT_DOWN_MASK) == MouseEvent.ALT_DOWN_MASK) {
307       if (leftButton) {
308         middleButton = true;
309         leftButton = false;
310       }
311     }
312     // Right Button
313     if ((mod & MouseEvent.BUTTON3_DOWN_MASK) == MouseEvent.BUTTON3_DOWN_MASK) {
314       rightButton = true;
315     }
316     // Alternatively, map "shift + button1" to the right button
317     if ((mod & MouseEvent.SHIFT_DOWN_MASK) == MouseEvent.SHIFT_DOWN_MASK) {
318       if (leftButton) {
319         rightButton = true;
320         leftButton = false;
321       }
322     }
323     int x = evt.getX();
324     int y = evt.getY();
325     atom = null;
326     axisSelected = false;
327     if (buttonPress) {
328       // Picking Results
329       pickCanvas.setShapeLocation(x, y);
330       // Once in a while "pickClosest" throws an exception due to
331       // not being able to invert a matrix??
332       // Catch and ignore this until a fix is determined...
333       try {
334         pickResult = pickCanvas.pickClosest();
335       } catch (Exception e) {
336         pickResult = null;
337       }
338       if (pickResult != null) {
339         SceneGraphPath sgp = pickResult.getSceneGraphPath();
340         Node node = sgp.getObject();
341         if (node instanceof Shape3D) {
342           Shape3D s = (Shape3D) node;
343           Object o = s.getUserData();
344           if (o instanceof MolecularAssembly) {
345             MolecularAssembly sys = (MolecularAssembly) o;
346             if (pickResult.numIntersections() > 0) {
347               PickIntersection pi = pickResult.getIntersection(0);
348               int[] coords = pi.getPrimitiveCoordinateIndices();
349               atom = sys.getAtomFromWireVertex(coords[0]);
350             }
351           } else if (o instanceof Atom) {
352             atom = (Atom) o;
353           } else if (o instanceof GraphicsAxis) {
354             axisSelected = true;
355           }
356         }
357       }
358     }
359   }
360 
361   private boolean systemRotate() {
362     TransformGroup transformGroup = getTransformGroup();
363     if (transformGroup != null) {
364       systemRotate.setTransformGroup(transformGroup);
365       postId(ROTATEPOST);
366       return true;
367     }
368     return false;
369   }
370 
371   private boolean systemTranslate() {
372     TransformGroup transformGroup = getTransformGroup();
373     if (transformGroup != null) {
374       systemTranslate.setTransformGroup(transformGroup);
375       postId(TRANSLATEPOST);
376       return true;
377     }
378     return false;
379   }
380 
381   private TransformGroup getTransformGroup() {
382     TransformGroup transformGroup = null;
383     GraphicsCanvas.MouseMode mouseMode = graphicsCanvas.getMouseMode();
384     if ((mouseMode == GraphicsCanvas.MouseMode.SYSTEMBELOWMOUSE) && atom != null) {
385       transformGroup = (TransformGroup) pickResult.getNode(PickResult.TRANSFORM_GROUP);
386     } else if (mouseMode == GraphicsCanvas.MouseMode.ACTIVESYSTEM) {
387       if (mainPanel.getHierarchy().getActive() != null) {
388         transformGroup = mainPanel.getHierarchy().getActive().getTransformGroup();
389       }
390     }
391     if (transformGroup != null) {
392       // Make sure we can read and write the TransformGroup.
393       if (!transformGroup.getCapability(TransformGroup.ALLOW_TRANSFORM_READ)
394           || !transformGroup.getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE)) {
395         transformGroup = null;
396       }
397     }
398     return transformGroup;
399   }
400 }