1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.ui;
39
40 import static ffx.numerics.math.DoubleMath.bondAngle;
41 import static ffx.numerics.math.DoubleMath.dihedralAngle;
42 import static ffx.numerics.math.DoubleMath.dist;
43 import static java.lang.String.format;
44 import static org.apache.commons.math3.util.FastMath.toDegrees;
45
46 import ffx.potential.MolecularAssembly;
47 import ffx.potential.bonded.Atom;
48 import ffx.potential.bonded.BondedTerm;
49 import ffx.potential.bonded.MSNode;
50 import ffx.potential.bonded.Molecule;
51 import ffx.potential.bonded.Polymer;
52 import ffx.potential.bonded.RendererCache;
53 import ffx.potential.bonded.Residue;
54 import ffx.ui.behaviors.PickMouseBehavior;
55
56 import java.util.Hashtable;
57 import java.util.List;
58 import java.util.Vector;
59 import java.util.logging.Logger;
60 import javax.swing.tree.TreePath;
61
62 import org.jogamp.java3d.Bounds;
63 import org.jogamp.java3d.BranchGroup;
64 import org.jogamp.java3d.Node;
65 import org.jogamp.java3d.SceneGraphPath;
66 import org.jogamp.java3d.Shape3D;
67 import org.jogamp.java3d.Transform3D;
68 import org.jogamp.java3d.utils.picking.PickCanvas;
69 import org.jogamp.java3d.utils.picking.PickIntersection;
70 import org.jogamp.java3d.utils.picking.PickResult;
71 import org.jogamp.vecmath.Vector3d;
72
73
74
75
76
77
78 public class GraphicsPicking extends PickMouseBehavior {
79
80
81
82
83 static final Hashtable<String, PickLevel> pickLevelHash = new Hashtable<>();
84
85 private static final Logger logger = Logger.getLogger(GraphicsPicking.class.getName());
86
87 static {
88 PickLevel[] values = PickLevel.values();
89 for (PickLevel value : values) {
90 pickLevelHash.put(value.toString(), value);
91 }
92 }
93
94 private final MainPanel mainPanel;
95
96 private boolean picking = false;
97
98 private PickLevel pickLevel = PickLevel.PICKATOM;
99 private PickLevel newPickLevel = PickLevel.PICKATOM;
100
101 private Atom previousAtom = null;
102
103 private int pickNumber = 0;
104
105 private MSNode previousPick = null;
106
107 private final Vector<Atom> atomCache = new Vector<>(4);
108 private int count = 0;
109
110 private final double[] a = new double[3];
111 private final double[] b = new double[3];
112 private final double[] c = new double[3];
113 private final double[] d = new double[3];
114 private final Transform3D systemTransform3D = new Transform3D();
115 private final Vector3d systemPosition = new Vector3d();
116 private final Vector3d atomPosition = new Vector3d();
117
118
119
120
121
122
123
124
125
126 public GraphicsPicking(
127 BranchGroup base, Bounds bounds, GraphicsCanvas graphicsCanvas, MainPanel mainPanel) {
128 super(graphicsCanvas, base, bounds);
129 this.mainPanel = mainPanel;
130 pickCanvas.setMode(PickCanvas.GEOMETRY);
131 pickCanvas.setTolerance(3.0f);
132 }
133
134
135
136
137 public void clear() {
138 if (previousPick != null) {
139 mainPanel.getHierarchy().collapsePath(new TreePath(previousPick.getPath()));
140 previousPick.setSelected(false);
141 previousPick.setColor(RendererCache.ColorModel.SELECT, null, null);
142 previousPick = null;
143 pickNumber = 0;
144 }
145 for (Atom a : atomCache) {
146 a.setSelected(false);
147 a.setColor(RendererCache.ColorModel.SELECT, null, null);
148 }
149 atomCache.clear();
150 }
151
152
153
154
155
156
157 public boolean getPicking() {
158 return picking;
159 }
160
161
162
163
164
165
166 public void setPicking(boolean m) {
167 picking = m;
168 if (!picking) {
169 clear();
170 }
171 }
172
173
174
175
176
177
178 public void updateScene(int xpos, int ypos) {
179 if (!picking) {
180 return;
181 }
182
183 pickCanvas.setShapeLocation(xpos, ypos);
184 PickResult result = pickCanvas.pickClosest();
185 if (result != null) {
186 SceneGraphPath sceneGraphPath = result.getSceneGraphPath();
187 Node node = sceneGraphPath.getObject();
188 if (!(node instanceof Shape3D pickedShape3D)) {
189 return;
190 }
191 Object userData = pickedShape3D.getUserData();
192 if (userData instanceof MolecularAssembly) {
193 FFXSystem sys = (FFXSystem) userData;
194 if (result.numIntersections() > 0) {
195 PickIntersection pickIntersection = result.getIntersection(0);
196 int[] coords = pickIntersection.getPrimitiveCoordinateIndices();
197 userData = sys.getAtomFromWireVertex(coords[0]);
198 } else {
199 return;
200 }
201 }
202 if (userData instanceof Atom atom) {
203
204 if (!(pickLevel == newPickLevel)) {
205 pickLevel = newPickLevel;
206 pickNumber = 0;
207 }
208
209 String pickLevelString = pickLevel.toString();
210 boolean measure = pickLevelString.startsWith("MEASURE");
211 if (!measure || count == 0) {
212 for (Atom matom : atomCache) {
213 matom.setSelected(false);
214 matom.setColor(RendererCache.ColorModel.SELECT, null, null);
215 }
216 atomCache.clear();
217 count = 0;
218 }
219
220 if (measure && !atomCache.contains(atom)) {
221 atomCache.add(0, atom);
222 atom.setSelected(true);
223 atom.setColor(RendererCache.ColorModel.PICK, null, null);
224 count++;
225 measure();
226 }
227 if (!measure) {
228
229
230 if (atom == previousAtom) {
231 pickNumber++;
232 } else {
233 previousAtom = atom;
234 pickNumber = 0;
235 }
236 MSNode currentPick = null;
237 switch (pickLevel) {
238 case PICKATOM:
239 currentPick = atom;
240 break;
241 case PICKBOND:
242 case PICKANGLE:
243 case PICKDIHEDRAL:
244 List<? extends BondedTerm> terms;
245 if (pickLevel == PickLevel.PICKBOND) {
246 terms = atom.getBonds();
247 } else if (pickLevel == PickLevel.PICKANGLE) {
248 terms = atom.getAngles();
249 } else {
250 terms = atom.getTorsions();
251 }
252 if (terms == null) {
253 return;
254 }
255 int num = terms.size();
256 if (pickNumber >= num) {
257 pickNumber = 0;
258 }
259 currentPick = terms.get(pickNumber);
260 break;
261 case PICKRESIDUE:
262 case PICKPOLYMER:
263 case PICKMOLECULE:
264 case PICKSYSTEM:
265 MSNode dataNode;
266 if (pickLevel == PickLevel.PICKRESIDUE) {
267 dataNode = atom.getMSNode(Residue.class);
268 } else if (pickLevel == PickLevel.PICKPOLYMER) {
269 dataNode = atom.getMSNode(Polymer.class);
270 } else if (pickLevel == PickLevel.PICKSYSTEM) {
271 dataNode = atom.getMSNode(MolecularAssembly.class);
272 } else {
273 dataNode = atom.getMSNode(Molecule.class);
274 if (dataNode == null) {
275 dataNode = atom.getMSNode(Polymer.class);
276 }
277 }
278 currentPick = dataNode;
279 break;
280 case MEASUREANGLE:
281 case MEASUREDIHEDRAL:
282 case MEASUREDISTANCE:
283 break;
284 }
285
286 if (currentPick != null) {
287 if (controlButton) {
288 mainPanel.getHierarchy().toggleSelection(currentPick);
289 } else if (currentPick != previousPick) {
290 mainPanel.getHierarchy().onlySelection(currentPick);
291 }
292
293 mainPanel
294 .getGraphics3D()
295 .updateScene(currentPick, false, false, null, true, RendererCache.ColorModel.PICK);
296 }
297
298 if (previousPick != null && previousPick != currentPick) {
299 previousPick.setColor(RendererCache.ColorModel.REVERT, null, null);
300 }
301 previousPick = currentPick;
302 }
303 }
304 }
305 }
306
307 private void distance(Atom atom, double[] pos) {
308 MolecularAssembly m = atom.getMSNode(MolecularAssembly.class);
309 m.getTransformGroup().getTransform(systemTransform3D);
310 systemTransform3D.get(systemPosition);
311 systemTransform3D.setScale(1.0d);
312 systemTransform3D.setTranslation(new Vector3d(0, 0, 0));
313 atom.getV3D(atomPosition);
314 systemTransform3D.transform(atomPosition);
315 atomPosition.add(systemPosition);
316 atomPosition.get(pos);
317 }
318
319
320
321
322
323
324 MSNode getPick() {
325 return previousPick;
326 }
327
328
329
330
331
332
333 String getPickLevel() {
334 return pickLevel.toString();
335 }
336
337
338
339
340
341
342 void setPickLevel(String newPick) {
343 if (pickLevelHash.containsKey(newPick.toUpperCase())) {
344 newPickLevel = pickLevelHash.get(newPick.toUpperCase());
345 }
346 }
347
348 private void measure() {
349 String measurement;
350 double value;
351 Atom a1, a2, a3, a4;
352 switch (pickLevel) {
353 case MEASUREDISTANCE -> {
354 if (atomCache.size() < 2) {
355 return;
356 }
357 a1 = atomCache.get(0);
358 a2 = atomCache.get(1);
359 distance(a1, a);
360 distance(a2, b);
361 value = dist(a, b);
362 measurement = "\nDistance\t" + a1.getIndex() + ", " + a2.getIndex() + ": \t" + format("%10.5f", value);
363 }
364 case MEASUREANGLE -> {
365 if (atomCache.size() < 3) {
366 return;
367 }
368 a1 = atomCache.get(0);
369 a2 = atomCache.get(1);
370 a3 = atomCache.get(2);
371 distance(a1, a);
372 distance(a2, b);
373 distance(a3, c);
374 value = bondAngle(a, b, c);
375 value = toDegrees(value);
376 measurement = "\nAngle\t" + a1.getIndex() + ", " + a2.getIndex() + ", " + a3.getIndex() + ": \t" + format("%10.5f", value);
377 }
378 case MEASUREDIHEDRAL -> {
379 if (atomCache.size() < 4) {
380 return;
381 }
382 a1 = atomCache.get(0);
383 a2 = atomCache.get(1);
384 a3 = atomCache.get(2);
385 a4 = atomCache.get(3);
386 distance(a1, a);
387 distance(a2, b);
388 distance(a3, c);
389 distance(a4, d);
390 value = dihedralAngle(a, b, c, d);
391 value = toDegrees(value);
392 measurement = "\nDihedral\t" + a1.getIndex() + ", " + a2.getIndex() + ", " + a3.getIndex()
393 + ", " + a4.getIndex() + ":\t" + format("%10.5f", value);
394 }
395 default -> {
396 return;
397 }
398 }
399 logger.info(measurement);
400 ModelingShell modelingShell = mainPanel.getModelingShell();
401 modelingShell.setMeasurement(measurement, value);
402 count = 0;
403 }
404
405
406
407
408 void resetCount() {
409 count = 0;
410 }
411
412 public enum PickLevel {
413 PICKATOM,
414 PICKBOND,
415 PICKANGLE,
416 PICKDIHEDRAL,
417 PICKRESIDUE,
418 PICKMOLECULE,
419 PICKPOLYMER,
420 PICKSYSTEM,
421 MEASUREDISTANCE,
422 MEASUREANGLE,
423 MEASUREDIHEDRAL
424 }
425 }