1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2006-12-18 10:29:29 -0600 (Mon, 18 Dec 2006) $
4  * $Revision: 6502 $
5  *
6  * Copyright (C) 2003-2005  The Jmol Development Team
7  *
8  * Contact: jmol-developers@lists.sf.net
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2.1 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 package org.jmol.viewer;
25 
26 import org.jmol.api.Interface;
27 import org.jmol.api.JmolNavigatorInterface;
28 import org.jmol.api.JmolScriptEvaluator;
29 import org.jmol.c.STER;
30 import javajs.util.BS;
31 import org.jmol.script.T;
32 import org.jmol.thread.JmolThread;
33 import org.jmol.util.Escape;
34 import org.jmol.util.Point3fi;
35 
36 import javajs.util.Lst;
37 import javajs.util.SB;
38 
39 import org.jmol.util.Logger;
40 import javajs.util.P3;
41 import javajs.util.P4;
42 import javajs.util.A4;
43 import javajs.util.M3;
44 import javajs.util.M4;
45 import javajs.util.P3i;
46 import javajs.util.Quat;
47 import javajs.util.T3;
48 import javajs.util.V3;
49 import org.jmol.util.Vibration;
50 
51 import java.util.Hashtable;
52 
53 import java.util.Map;
54 
55 public class TransformManager {
56 
57   protected Viewer vwr;
58 
59   final static int DEFAULT_SPIN_Y = 30;
60   final static int DEFAULT_SPIN_FPS = 30;
61   static final int DEFAULT_NAV_FPS = 10;
62   public static final float DEFAULT_VISUAL_RANGE = 5;
63   public final static int DEFAULT_STEREO_DEGREES = -5;
64 
65   public final static int MODE_STANDARD = 0;
66   public final static int MODE_NAVIGATION = 1;
67   public final static int MODE_PERSPECTIVE_PYMOL = 2;
68 
69   static final int DEFAULT_PERSPECTIVE_MODEL = 11;
70   static final boolean DEFAULT_PERSPECTIVE_DEPTH = true;
71   static final float DEFAULT_CAMERA_DEPTH = 3.0f;
72 
73   public JmolThread movetoThread;
74   public JmolThread vibrationThread;
75   public JmolThread spinThread;
76 
77   public final static double degreesPerRadian = 180 / Math.PI;
78 
79   protected int perspectiveModel = DEFAULT_PERSPECTIVE_MODEL;
80   protected float cameraScaleFactor;
81   public float referencePlaneOffset;
82   protected float aperatureAngle;
83   protected float cameraDistanceFromCenter;
84   public float modelCenterOffset;
85   public float modelRadius;
86   public float modelRadiusPixels;
87 
88   public final P3 navigationCenter = new P3();
89   public final P3 navigationOffset = new P3();
90   public final P3 navigationShiftXY = new P3();
91   public float navigationDepthPercent;
92 
93   protected final M4 matrixTemp = new M4();
94   protected final V3 vectorTemp = new V3();
95 
TransformManager()96   public TransformManager() {
97   }
98 
getTransformManager(Viewer vwr, int width, int height, boolean is4D)99   static TransformManager getTransformManager(Viewer vwr, int width,
100                                               int height, boolean is4D) {
101     TransformManager me = (is4D ? (TransformManager) Interface.getInterface(
102         "org.jmol.viewer.TransformManager4D", vwr, "tm")
103         : new TransformManager());
104     me.vwr = vwr;
105     me.setScreenParameters(width, height, true, false, true, true);
106     return me;
107   }
108 
109   /* ***************************************************************
110    * GENERAL METHODS
111    ***************************************************************/
112 
setDefaultPerspective()113   void setDefaultPerspective() {
114     setCameraDepthPercent(DEFAULT_CAMERA_DEPTH, true);
115     setPerspectiveDepth(DEFAULT_PERSPECTIVE_DEPTH);
116     setStereoDegrees(DEFAULT_STEREO_DEGREES);
117     visualRangeAngstroms = DEFAULT_VISUAL_RANGE;
118     setSpinOff();
119     setVibrationPeriod(0);
120   }
121 
homePosition(boolean resetSpin)122   public void homePosition(boolean resetSpin) {
123     // reset, setNavigationMode, setPerspectiveModel
124     if (resetSpin)
125       setSpinOff();
126     setNavOn(false);
127     navFps = DEFAULT_NAV_FPS;
128     navX = navY = navZ = 0;
129     rotationCenterDefault.setT(vwr.getBoundBoxCenter());
130     setFixedRotationCenter(rotationCenterDefault);
131     rotationRadiusDefault = setRotationRadius(0, true);
132     windowCentered = true;
133     setRotationCenterAndRadiusXYZ(null, true);
134     resetRotation();
135     //if (vwr.autoLoadOrientation()) {
136     M3 m = (M3) vwr.ms.getInfoM("defaultOrientationMatrix");
137     if (m != null)
138       setRotation(m);
139     //}
140     setZoomEnabled(true);
141     zoomToPercent(vwr.g.modelKitMode ? 50 : 100);
142     zmPct = zmPctSet;
143     slabReset();
144     resetFitToScreen(true);
145     if (vwr.isJmolDataFrame()) {
146       fixedRotationCenter.set(0, 0, 0);
147     } else {
148       if (vwr.g.axesOrientationRasmol)
149         matrixRotate.setAsXRotation((float) Math.PI);
150     }
151     vwr.stm.saveOrientation("default", null);
152     if (mode == MODE_NAVIGATION)
153       setNavigationMode(true);
154   }
155 
setRotation(M3 m)156   public void setRotation(M3 m) {
157     if (m.isRotation())
158       matrixRotate.setM3(m);
159     else
160       resetRotation();
161   }
162 
resetRotation()163   public void resetRotation() {
164     matrixRotate.setScale(1); // no rotations
165   }
166 
clearThreads()167   void clearThreads() {
168     clearVibration();
169     clearSpin();
170     setNavOn(false);
171     stopMotion();
172   }
173 
clear()174   void clear() {
175     fixedRotationCenter.set(0, 0, 0);
176     navigating = false;
177     slabPlane = null;
178     depthPlane = null;
179     zSlabPoint = null;
180     resetNavigationPoint(true);
181   }
182 
183   protected boolean haveNotifiedNaN = false;
184 
185   public float spinX;
186 
187   public float spinY = DEFAULT_SPIN_Y;
188 
189   public float spinZ;
190 
191   public float spinFps = DEFAULT_SPIN_FPS;
192   public float navX;
193   public float navY;
194   public float navZ;
195   public float navFps = Float.NaN;
196 
197   public boolean isSpinInternal = false;
198   public boolean isSpinFixed = false;
199   boolean isSpinSelected = false;
200   protected boolean doTransform4D;
201 
202   public final P3 fixedRotationOffset = new P3();
203   public final P3 fixedRotationCenter = new P3();
204   protected final P3 perspectiveOffset = new P3();
205   protected final P3 perspectiveShiftXY = new P3();
206 
207   private final P3 rotationCenterDefault = new P3();
208   private float rotationRadiusDefault;
209 
210   public final A4 fixedRotationAxis = new A4();
211   public final A4 internalRotationAxis = new A4();
212   protected V3 internalTranslation;
213   final P3 internalRotationCenter = P3.new3(0, 0, 0);
214   private float internalRotationAngle = 0;
215 
216   /* ***************************************************************
217    * ROTATIONS
218    ***************************************************************/
219 
220   // this matrix only holds rotations ... no translations
221   public final M3 matrixRotate = new M3();
222 
223   protected final M3 matrixTemp3 = new M3();
224   private final M4 matrixTemp4 = new M4();
225   private final A4 axisangleT = new A4();
226   private final V3 vectorT = new V3();
227   private final V3 vectorT2 = new V3();
228   private final P3 pointT2 = new P3();
229 
230   public final static int MAXIMUM_ZOOM_PERCENTAGE = 200000;
231   private final static int MAXIMUM_ZOOM_PERSPECTIVE_DEPTH = 10000;
232 
setFixedRotationCenter(T3 center)233   private void setFixedRotationCenter(T3 center) {
234     if (center == null)
235       return;
236     fixedRotationCenter.setT(center);
237   }
238 
setRotationPointXY(P3 center)239   void setRotationPointXY(P3 center) {
240     P3i newCenterScreen = transformPt(center);
241     fixedTranslation.set(newCenterScreen.x, newCenterScreen.y, 0);
242   }
243 
244   V3 rotationAxis = new V3();
245   float rotationRate = 0;
246 
spinXYBy(int xDelta, int yDelta, float speed)247   void spinXYBy(int xDelta, int yDelta, float speed) {
248     // from mouse action
249     if (xDelta == 0 && yDelta == 0) {
250       if (spinThread != null && spinIsGesture)
251         clearSpin();
252       return;
253     }
254     clearSpin();
255     P3 pt1 = P3.newP(fixedRotationCenter);
256     P3 ptScreen = new P3();
257     transformPt3f(pt1, ptScreen);
258     P3 pt2 = P3.new3(-yDelta, xDelta, 0);
259     pt2.add(ptScreen);
260     unTransformPoint(pt2, pt2);
261     vwr.setInMotion(false);
262     rotateAboutPointsInternal(null, pt2, pt1, 10 * speed, Float.NaN, false,
263         true, null, true, null, null, null, null);
264   }
265 
266   //  final V3 arcBall0 = new V3();
267   //  final V3 arcBall1 = new V3();
268   //  final V3 arcBallAxis = new V3();
269   //  final M3 arcBall0Rotation = new M3();
270 
271   //  void rotateArcBall(float x, float y, float factor) {
272   //    // radius is half the screen pixel count.
273   //    float radius2 = (screenPixelCount >> 2) * screenPixelCount;
274   //    x -= fixedTranslation.x;
275   //    y -= fixedTranslation.y;
276   //    float z = radius2 - x * x - y * y;
277   //    z = (z < 0 ? -1 : 1) * (float) Math.sqrt(Math.abs(z));
278   //    if (factor == 0) {
279   //      // mouse down sets the initial rotation and point on the sphere
280   //      arcBall0Rotation.setM3(matrixRotate);
281   //      arcBall0.set(x, -y, z);
282   //      if (!Float.isNaN(z))
283   //        arcBall0.normalize();
284   //      return;
285   //    }
286   //    if (Float.isNaN(arcBall0.z) || Float.isNaN(z))
287   //      return;
288   //    arcBall1.set(x, -y, z);
289   //    arcBall1.normalize();
290   //    arcBallAxis.cross(arcBall0, arcBall1);
291   //    axisangleT.setVA(arcBallAxis, factor
292   //        * (float) Math.acos(arcBall0.dot(arcBall1)));
293   //    setRotation(arcBall0Rotation);
294   //    rotateAxisAngle2(axisangleT, null);
295   //  }
296 
rotateXYBy(float degX, float degY, BS bsAtoms)297   protected void rotateXYBy(float degX, float degY, BS bsAtoms) {
298     // from mouse action
299     //if (vwr.getTestFlag(2)) {
300     //  rotateXRadians(degY * JC.radiansPerDegree, bsAtoms);
301     //  rotateYRadians(degX * JC.radiansPerDegree, bsAtoms);
302     //} else {
303     rotate3DBall(degX, degY, bsAtoms);
304     //}
305   }
306 
rotateZBy(int zDelta, int x, int y)307   void rotateZBy(int zDelta, int x, int y) {
308     if (x != Integer.MAX_VALUE && y != Integer.MAX_VALUE)
309       resetXYCenter(x, y);
310     rotateZRadians((float) (zDelta / degreesPerRadian));
311   }
312 
applyRotation(M3 mNew, boolean isInternal, BS bsAtoms, V3 translation, boolean translationOnly, M4 m4)313   private void applyRotation(M3 mNew, boolean isInternal, BS bsAtoms,
314                              V3 translation, boolean translationOnly, M4 m4) {
315     if (bsAtoms == null) {
316       matrixRotate.mul2(mNew, matrixRotate);
317       return;
318     }
319     vwr.moveAtoms(m4, mNew, matrixRotate, translation, internalRotationCenter,
320         isInternal, bsAtoms, translationOnly);
321     if (translation != null) {
322       internalRotationCenter.add(translation);
323     }
324   }
325 
rotate3DBall(float xDeg, float yDeg, BS bsAtoms)326   protected void rotate3DBall(float xDeg, float yDeg, BS bsAtoms) {
327     // xDeg and yDeg are calibrated to be 180 degrees for
328     // a full drag across the frame or from top to bottom.
329 
330     // Note: We will apply this matrix to the untransformed
331     // model coordinates, not their screen counterparts.
332     // Nonetheless, dx and dy are in terms of the screen.
333     // The swapping of dx and dy, and their reversal in sign
334     // probably has to do with the fact that we are changing
335     // the signs of both screen Y and screen Z in the end.
336 
337     if (matrixTemp3.setAsBallRotation(JC.radiansPerDegree, -yDeg, -xDeg))
338       applyRotation(matrixTemp3, false, bsAtoms, null, false, null);
339   }
340 
rotateXRadians(float angleRadians, BS bsAtoms)341   public synchronized void rotateXRadians(float angleRadians, BS bsAtoms) {
342     applyRotation(matrixTemp3.setAsXRotation(angleRadians), false, bsAtoms,
343         null, false, null);
344   }
345 
rotateYRadians(float angleRadians, BS bsAtoms)346   public synchronized void rotateYRadians(float angleRadians, BS bsAtoms) {
347     applyRotation(matrixTemp3.setAsYRotation(angleRadians), false, bsAtoms,
348         null, false, null);
349   }
350 
rotateZRadians(float angleRadians)351   public synchronized void rotateZRadians(float angleRadians) {
352     applyRotation(matrixTemp3.setAsZRotation(angleRadians), false, null, null,
353         false, null);
354   }
355 
rotateAxisAngle(V3 rotAxis, float radians)356   public void rotateAxisAngle(V3 rotAxis, float radians) {
357     axisangleT.setVA(rotAxis, radians);
358     rotateAxisAngle2(axisangleT, null);
359   }
360 
rotateAxisAngle2(A4 axisAngle, BS bsAtoms)361   private synchronized void rotateAxisAngle2(A4 axisAngle, BS bsAtoms) {
362     applyRotation(matrixTemp3.setAA(axisAngle), false, bsAtoms, null, false, null);
363   }
364 
365   /*
366    * *************************************************************** *THE* TWO
367    * VIEWER INTERFACE METHODS
368    * **************************************************************
369    */
370 
rotateAxisAngleAtCenter(JmolScriptEvaluator eval, P3 rotCenter, V3 rotAxis, float degreesPerSecond, float endDegrees, boolean isSpin, BS bsAtoms)371   boolean rotateAxisAngleAtCenter(JmolScriptEvaluator eval, P3 rotCenter,
372                                   V3 rotAxis, float degreesPerSecond,
373                                   float endDegrees, boolean isSpin, BS bsAtoms) {
374 
375     // *THE* Viewer FIXED frame rotation/spinning entry point
376     if (rotCenter != null)
377       moveRotationCenter(rotCenter, true);
378 
379     if (isSpin)
380       setSpinOff();
381     setNavOn(false);
382 
383     if (vwr.headless) {
384       if (isSpin && endDegrees == Float.MAX_VALUE)
385         return false;
386       isSpin = false;
387     }
388     if (Float.isNaN(degreesPerSecond) || degreesPerSecond == 0
389         || endDegrees == 0)
390       return false;
391 
392     if (rotCenter != null) {
393       setRotationPointXY(rotCenter);
394     }
395     setFixedRotationCenter(rotCenter);
396     rotationAxis.setT(rotAxis);
397     rotationRate = degreesPerSecond;
398     if (isSpin) {
399       fixedRotationAxis.setVA(rotAxis, degreesPerSecond * JC.radiansPerDegree);
400       isSpinInternal = false;
401       isSpinFixed = true;
402       isSpinSelected = (bsAtoms != null);
403       setSpin(eval, true, endDegrees, null, null, bsAtoms, false);
404       // fixed spin -- we will wait
405       return (endDegrees != Float.MAX_VALUE);
406     }
407     float radians = endDegrees * JC.radiansPerDegree;
408     fixedRotationAxis.setVA(rotAxis, endDegrees);
409     rotateAxisAngleRadiansFixed(radians, bsAtoms);
410     return true;
411   }
412 
rotateAxisAngleRadiansFixed(float angleRadians, BS bsAtoms)413   public synchronized void rotateAxisAngleRadiansFixed(float angleRadians,
414                                                        BS bsAtoms) {
415     // for spinning -- reduced number of radians
416     axisangleT.setAA(fixedRotationAxis);
417     axisangleT.angle = angleRadians;
418     rotateAxisAngle2(axisangleT, bsAtoms);
419   }
420 
421   /*
422    * *************************************************************** INTERNAL
423    * ROTATIONS**************************************************************
424    */
425 
426   /**
427    *
428    * @param eval
429    * @param point1
430    * @param point2
431    * @param degreesPerSecond
432    * @param endDegrees
433    * @param isClockwise
434    * @param isSpin
435    * @param bsAtoms
436    * @param isGesture
437    * @param translation
438    * @param finalPoints
439    * @param dihedralList
440    * @param m4
441    * @return true if synchronous so that JavaScript can restart properly
442    */
rotateAboutPointsInternal(JmolScriptEvaluator eval, T3 point1, T3 point2, float degreesPerSecond, float endDegrees, boolean isClockwise, boolean isSpin, BS bsAtoms, boolean isGesture, V3 translation, Lst<P3> finalPoints, float[] dihedralList, M4 m4)443   boolean rotateAboutPointsInternal(JmolScriptEvaluator eval, T3 point1,
444                                     T3 point2, float degreesPerSecond,
445                                     float endDegrees, boolean isClockwise,
446                                     boolean isSpin, BS bsAtoms,
447                                     boolean isGesture, V3 translation,
448                                     Lst<P3> finalPoints, float[] dihedralList, M4 m4) {
449 
450     // *THE* Viewer INTERNAL frame rotation entry point
451 
452     if (isSpin)
453       setSpinOff();
454     setNavOn(false);
455 
456     if (dihedralList == null
457         && (translation == null || translation.length() < 0.001)
458         && (isSpin ? Float.isNaN(degreesPerSecond) || degreesPerSecond == 0
459             : endDegrees == 0))
460       return false;
461 
462     V3 axis = null;
463     if (dihedralList == null) {
464       axis = V3.newVsub(point2, point1);
465       if (isClockwise)
466         axis.scale(-1f);
467       internalRotationCenter.setT(point1);
468       rotationAxis.setT(axis);
469       internalTranslation = (translation == null ? null : V3.newV(translation));
470     }
471     boolean isSelected = (bsAtoms != null);
472     if (isSpin) {
473       // we need to adjust the degreesPerSecond to match a multiple of the frame rate
474       if (dihedralList == null) {
475         if (endDegrees == 0)
476           endDegrees = Float.NaN;
477         if (Float.isNaN(endDegrees)) {
478           rotationRate = degreesPerSecond;
479         } else {
480           int nFrames = (int) (Math.abs(endDegrees)
481               / Math.abs(degreesPerSecond) * spinFps + 0.5);
482           rotationRate = degreesPerSecond = endDegrees / nFrames * spinFps;
483           if (translation != null)
484             internalTranslation.scale(1f / nFrames);
485         }
486         internalRotationAxis.setVA(axis, (Float.isNaN(rotationRate) ? 0
487             : rotationRate) * JC.radiansPerDegree);
488         isSpinInternal = true;
489         isSpinFixed = false;
490         isSpinSelected = isSelected;
491       } else {
492         endDegrees = degreesPerSecond;
493       }
494       setSpin(eval, true, endDegrees, finalPoints, dihedralList, bsAtoms,
495           isGesture);
496       return !Float.isNaN(endDegrees);
497     }
498     float radians = endDegrees * JC.radiansPerDegree;
499     internalRotationAxis.setVA(axis, radians);
500     rotateAxisAngleRadiansInternal(radians, bsAtoms, m4);
501     return false;
502   }
503 
rotateAxisAngleRadiansInternal(float radians, BS bsAtoms, M4 m4)504   public synchronized void rotateAxisAngleRadiansInternal(float radians,
505                                                           BS bsAtoms, M4 m4) {
506 
507     // final matrix rotation when spinning or just rotating
508 
509     // trick is to apply the current rotation to the internal rotation axis
510     // and then save the angle for generating a new fixed point later
511     internalRotationAngle = radians;
512     vectorT.set(internalRotationAxis.x, internalRotationAxis.y,
513         internalRotationAxis.z);
514     matrixRotate.rotate2(vectorT, vectorT2);
515     axisangleT.setVA(vectorT2, radians);
516 
517     // NOW apply that rotation
518 
519     applyRotation(matrixTemp3.setAA(axisangleT), true, bsAtoms,
520         internalTranslation, radians > 1e6f, m4);
521     if (bsAtoms == null)
522       getNewFixedRotationCenter();
523   }
524 
getNewFixedRotationCenter()525   void getNewFixedRotationCenter() {
526 
527     /*
528      * (1) determine vector offset VectorT ()
529      * (2) translate old point so trueRotationPt is at [0,0,0] (old - true)
530      * (3) do axisangle rotation of -radians (pointT2)
531      * (4) translate back (pointT2 + vectorT)
532      *
533      * The new position of old point is the new rotation center
534      * set this, rotate about it, and it will APPEAR that the
535      * rotation was about the desired point and axis!
536      *
537      */
538 
539     // fractional OPPOSITE of angle of rotation
540     axisangleT.setAA(internalRotationAxis);
541     axisangleT.angle = -internalRotationAngle;
542     //this is a fraction of the original for spinning
543     matrixTemp4.setToAA(axisangleT);
544 
545     // apply this to the fixed center point in the internal frame
546 
547     vectorT.setT(internalRotationCenter);
548     pointT2.sub2(fixedRotationCenter, vectorT);
549     T3 pt = matrixTemp4.rotTrans2(pointT2, new P3());
550 
551     // return this point to the fixed frame
552 
553     pt.add(vectorT);
554 
555     // it is the new fixed rotation center!
556 
557     setRotationCenterAndRadiusXYZ(pt, false);
558   }
559 
560   /* ***************************************************************
561    * TRANSLATIONS
562    ****************************************************************/
563   public final P3 fixedTranslation = new P3();
564   public final P3 camera = new P3();
565   public final P3 cameraSetting = new P3();
566 
567   float xTranslationFraction = 0.5f;
568   float yTranslationFraction = 0.5f;
569   protected float prevZoomSetting;
570 
571   public float previousX;
572   public float previousY;
573 
setTranslationFractions()574   void setTranslationFractions() {
575     xTranslationFraction = fixedTranslation.x / width;
576     yTranslationFraction = fixedTranslation.y / height;
577   }
578 
centerAt(int x, int y, P3 pt)579   public void centerAt(int x, int y, P3 pt) {
580     if (pt == null) {
581       translateXYBy(x, y);
582       return;
583     }
584     if (windowCentered)
585       vwr.setBooleanProperty("windowCentered", false);
586     fixedTranslation.x = x;
587     fixedTranslation.y = y;
588     setFixedRotationCenter(pt);
589   }
590 
percentToPixels(char xyz, float percent)591   public int percentToPixels(char xyz, float percent) {
592     switch (xyz) {
593     case 'x':
594       return (int) Math.floor(percent / 100 * width);
595     case 'y':
596       return (int) Math.floor(percent / 100 * height);
597     case 'z':
598       return (int) Math.floor(percent / 100 * screenPixelCount);
599     }
600     return 0;
601   }
602 
angstromsToPixels(float distance)603   int angstromsToPixels(float distance) {
604     return (int) Math.floor(scalePixelsPerAngstrom * distance);
605   }
606 
translateXYBy(int xDelta, int yDelta)607   void translateXYBy(int xDelta, int yDelta) {
608     // mouse action or translate x|y|z x.x nm|angstroms|%
609     fixedTranslation.x += xDelta;
610     fixedTranslation.y += yDelta;
611     setTranslationFractions();
612   }
613 
setCamera(float x, float y)614   public void setCamera(float x, float y) {
615     cameraSetting.set(x, y, (x == 0 && y == 0 ? 0 : 1));
616   }
617 
translateToPercent(char type, float percent)618   public void translateToPercent(char type, float percent) {
619     switch (type) {
620     case 'x':
621       xTranslationFraction = 0.5f + percent / 100;
622       fixedTranslation.x = width * xTranslationFraction;
623       return;
624     case 'y':
625       yTranslationFraction = 0.5f + percent / 100;
626       fixedTranslation.y = height * yTranslationFraction;
627       return;
628     case 'z':
629       if (mode == MODE_NAVIGATION)
630         setNavigationDepthPercent(percent);
631       return;
632     }
633   }
634 
getTranslationXPercent()635   public float getTranslationXPercent() {
636     return (width == 0 ? 0 : (fixedTranslation.x - width / 2f) * 100 / width);
637   }
638 
getTranslationYPercent()639   public float getTranslationYPercent() {
640     return (height == 0 ? 0 : (fixedTranslation.y - height / 2f) * 100 / height);
641   }
642 
getTranslationScript()643   public String getTranslationScript() {
644     String info = "";
645     float f = getTranslationXPercent();
646     if (f != 0.0)
647       info += "translate x " + f + ";";
648     f = getTranslationYPercent();
649     if (f != 0.0)
650       info += "translate y " + f + ";";
651     return info;
652   }
653 
getOrientationText(int type, boolean isBest)654   String getOrientationText(int type, boolean isBest) {
655     switch (type) {
656     case T.moveto:
657       return getMoveToText(1, false);
658     case T.rotation:
659       Quat q = getRotationQ();
660       if (isBest)
661         q = q.inv();
662       return q.toString();
663     case T.translation:
664       SB sb = new SB();
665       float d = getTranslationXPercent();
666       truncate2(sb, (isBest ? -d : d));
667       d = getTranslationYPercent();
668       truncate2(sb, (isBest ? -d : d));
669       return sb.toString();
670     default:
671       return getMoveToText(1, true) + "\n#OR\n" + getRotateZyzText(true);
672 
673     }
674   }
675 
getRotationQ()676   public Quat getRotationQ() {
677     return Quat.newM(matrixRotate);
678   }
679 
getOrientationInfo()680   Map<String, Object> getOrientationInfo() {
681     Map<String, Object> info = new Hashtable<String, Object>();
682     info.put("moveTo", getMoveToText(1, false));
683     info.put("center", "center " + getCenterText());
684     info.put("centerPt", fixedRotationCenter);
685     A4 aa = new A4();
686     aa.setM(matrixRotate);
687     info.put("axisAngle", aa);
688     info.put("quaternion", getRotationQ().toPoint4f());
689     info.put("rotationMatrix", matrixRotate);
690     info.put("rotateZYZ", getRotateZyzText(false));
691     info.put("rotateXYZ", getRotateXyzText());
692     info.put("transXPercent", Float.valueOf(getTranslationXPercent()));
693     info.put("transYPercent", Float.valueOf(getTranslationYPercent()));
694     info.put("zoom", Float.valueOf(zmPct));
695     info.put("modelRadius", Float.valueOf(modelRadius));
696     if (mode == MODE_NAVIGATION) {
697       info.put("navigationCenter",
698           "navigate center " + Escape.eP(navigationCenter));
699       info.put("navigationOffsetXPercent",
700           Float.valueOf(getNavigationOffsetPercent('X')));
701       info.put("navigationOffsetYPercent",
702           Float.valueOf(getNavigationOffsetPercent('Y')));
703       info.put("navigationDepthPercent",
704           Float.valueOf(navigationDepthPercent));
705     }
706     return info;
707   }
708 
getRotation(M3 m)709   public void getRotation(M3 m) {
710     // hmm ... I suppose that there could be a race condition here
711     // if matrixRotate is being modified while this is called
712     m.setM3(matrixRotate);
713   }
714 
715   /* ***************************************************************
716    * ZOOM
717    ****************************************************************/
718   public boolean zoomEnabled = true;
719 
720   /**
721    * zoom percent
722    *
723    * zmPct is the current displayed zoom value, AFTER rendering;
724    * may not be the same as zmPctSet, particularly if zoom is not enabled
725    *
726    */
727   public float zmPct = 100;
728 
729   /**
730    * zoom percent setting
731    *
732    * the current setting of zoom;
733    * may not be the same as zmPct, particularly  if zoom is not enabled
734    *
735    */
736   float zmPctSet = 100;
737 
setZoomHeight(boolean zoomHeight, boolean zoomLarge)738   public void setZoomHeight(boolean zoomHeight, boolean zoomLarge) {
739     this.zoomHeight = zoomHeight;
740     scaleFitToScreen(false, zoomLarge, false, true);
741   }
742 
743   private float zoomRatio;
744 
745   /**
746    * standard response to user mouse vertical shift-drag
747    *
748    * @param pixels
749    */
zoomBy(int pixels)750   protected void zoomBy(int pixels) {
751     if (pixels > 20)
752       pixels = 20;
753     else if (pixels < -20)
754       pixels = -20;
755     float deltaPercent = pixels * zmPctSet / 50;
756     if (deltaPercent == 0)
757       deltaPercent = (pixels > 0 ? 1 : (deltaPercent < 0 ? -1 : 0));
758     zoomRatio = (deltaPercent + zmPctSet) / zmPctSet;
759     zmPctSet += deltaPercent;
760   }
761 
zoomByFactor(float factor, int x, int y)762   void zoomByFactor(float factor, int x, int y) {
763     if (factor <= 0 || !zoomEnabled)
764       return;
765     if (mode != MODE_NAVIGATION) {
766       zoomRatio = factor;
767       zmPctSet *= factor;
768       resetXYCenter(x, y);
769     } else if (getNav()) {
770       nav.zoomByFactor(factor, x, y);
771     }
772   }
773 
zoomToPercent(float percentZoom)774   public void zoomToPercent(float percentZoom) {
775     zmPctSet = percentZoom;
776     zoomRatio = 0;
777   }
778 
translateZBy(int pixels)779   void translateZBy(int pixels) {
780     if (pixels >= screenPixelCount)
781       return;
782     float sppa = scalePixelsPerAngstrom
783         / (1 - pixels * 1.0f / screenPixelCount);
784     if (sppa >= screenPixelCount)
785       return;
786     float newZoomPercent = sppa / scaleDefaultPixelsPerAngstrom * 100f;
787     zoomRatio = newZoomPercent / zmPctSet;
788     zmPctSet = newZoomPercent;
789   }
790 
resetXYCenter(int x, int y)791   private void resetXYCenter(int x, int y) {
792     if (x == Integer.MAX_VALUE || y == Integer.MAX_VALUE)
793       return;
794     if (windowCentered)
795       vwr.setBooleanProperty("windowCentered", false);
796     P3 pt = new P3();
797     transformPt3f(fixedRotationCenter, pt);
798     pt.set(x, y, pt.z);
799     unTransformPoint(pt, pt);
800     fixedTranslation.set(x, y, 0);
801     setFixedRotationCenter(pt);
802   }
803 
zoomByPercent(float percentZoom)804   void zoomByPercent(float percentZoom) {
805     float deltaPercent = percentZoom * zmPctSet / 100;
806     if (deltaPercent == 0)
807       deltaPercent = (percentZoom < 0) ? -1 : 1;
808     zoomRatio = (deltaPercent + zmPctSet) / zmPctSet;
809     zmPctSet += deltaPercent;
810   }
811 
setScaleAngstromsPerInch(float angstromsPerInch)812   void setScaleAngstromsPerInch(float angstromsPerInch) {
813     // not compatible with perspectiveDepth
814     scale3D = (angstromsPerInch > 0);
815     if (scale3D)
816       scale3DAngstromsPerInch = angstromsPerInch;
817     perspectiveDepth = !scale3D;
818   }
819 
820   /* ***************************************************************
821    * SLAB
822    ****************************************************************/
823 
824   /*
825    slab is a term defined and used in rasmol.
826    it is a z-axis clipping plane. only atoms behind the slab get rendered.
827    100% means:
828    - the slab is set to z==0
829    - 100% of the molecule will be shown
830    50% means:
831    - the slab is set to the center of rotation of the molecule
832    - only the atoms behind the center of rotation are shown
833    0% means:
834    - the slab is set behind the molecule
835    - 0% (nothing, nada, nil, null) gets shown
836    */
837 
838   public boolean slabEnabled;
839   public boolean zShadeEnabled;
840 
841   public boolean internalSlab;
842 
843   int slabPercentSetting;
844   int depthPercentSetting;
845   public int slabValue;
846   public int depthValue;
847 
848   public int zSlabPercentSetting = 50; // new default for 12.3.6 and 12.2.6
849   public int zDepthPercentSetting = 0;
850   public P3 zSlabPoint;
851   public int zSlabValue;
852   public int zDepthValue;
853 
854   float slabRange = 0f;
855 
setSlabRange(float value)856   public void setSlabRange(float value) {
857     slabRange = value;
858   }
859 
setSlabEnabled(boolean slabEnabled)860   void setSlabEnabled(boolean slabEnabled) {
861     vwr.g.setB("slabEnabled", this.slabEnabled = slabEnabled);
862   }
863 
setZShadeEnabled(boolean zShadeEnabled)864   void setZShadeEnabled(boolean zShadeEnabled) {
865     this.zShadeEnabled = zShadeEnabled;
866     vwr.g.setB("zShade", zShadeEnabled);
867   }
868 
setZoomEnabled(boolean zoomEnabled)869   void setZoomEnabled(boolean zoomEnabled) {
870     this.zoomEnabled = zoomEnabled;
871     vwr.g.setB("zoomEnabled", zoomEnabled);
872   }
873 
874   P4 slabPlane = null;
875   P4 depthPlane = null;
876 
slabReset()877   public void slabReset() {
878     slabToPercent(100);
879     depthToPercent(0);
880     depthPlane = null;
881     slabPlane = null;
882     setSlabEnabled(false);
883     setZShadeEnabled(false);
884     slabDepthChanged();
885   }
886 
getSlabPercentSetting()887   public int getSlabPercentSetting() {
888     return slabPercentSetting;
889   }
890 
slabDepthChanged()891   private void slabDepthChanged() {
892     vwr.g.setI("slab", slabPercentSetting);
893     vwr.g.setI("depth", depthPercentSetting);
894     finalizeTransformParameters(); // also sets _slabPlane and _depthPlane
895   }
896 
slabByPercentagePoints(int percentage)897   void slabByPercentagePoints(int percentage) {
898     slabPlane = null;
899     if (percentage < 0 ? slabPercentSetting <= Math.max(0, depthPercentSetting)
900         : slabPercentSetting >= 100)
901       return;
902     slabPercentSetting += percentage;
903     slabDepthChanged();
904     if (depthPercentSetting >= slabPercentSetting)
905       depthPercentSetting = slabPercentSetting - 1;
906   }
907 
depthByPercentagePoints(int percentage)908   void depthByPercentagePoints(int percentage) {
909     depthPlane = null;
910     if (percentage < 0 ? depthPercentSetting <= 0
911         : depthPercentSetting >= Math.min(100, slabPercentSetting))
912       return;
913     depthPercentSetting += percentage;
914     if (slabPercentSetting <= depthPercentSetting)
915       slabPercentSetting = depthPercentSetting + 1;
916     slabDepthChanged();
917   }
918 
slabDepthByPercentagePoints(int percentage)919   void slabDepthByPercentagePoints(int percentage) {
920     slabPlane = null;
921     depthPlane = null;
922     if (percentage < 0 ? slabPercentSetting <= Math.max(0, depthPercentSetting)
923         : depthPercentSetting >= Math.min(100, slabPercentSetting))
924       return;
925     slabPercentSetting += percentage;
926     depthPercentSetting += percentage;
927     slabDepthChanged();
928   }
929 
slabToPercent(int percentSlab)930   public void slabToPercent(int percentSlab) {
931     slabPlane = null;
932     vwr.setFloatProperty("slabRange", 0);
933     slabPercentSetting = percentSlab;
934     if (depthPercentSetting >= slabPercentSetting)
935       depthPercentSetting = slabPercentSetting - 1;
936     slabDepthChanged();
937   }
938 
depthToPercent(int percentDepth)939   public void depthToPercent(int percentDepth) {
940     depthPlane = null;
941     vwr.g.setI("depth", percentDepth);
942     depthPercentSetting = percentDepth;
943     if (slabPercentSetting <= depthPercentSetting)
944       slabPercentSetting = depthPercentSetting + 1;
945     slabDepthChanged();
946   }
947 
zSlabToPercent(int percentSlab)948   void zSlabToPercent(int percentSlab) {
949     zSlabPercentSetting = percentSlab;
950     if (zDepthPercentSetting > zSlabPercentSetting)
951       zDepthPercentSetting = percentSlab;
952   }
953 
zDepthToPercent(int percentDepth)954   void zDepthToPercent(int percentDepth) {
955     zDepthPercentSetting = percentDepth;
956     if (zDepthPercentSetting > zSlabPercentSetting)
957       zSlabPercentSetting = percentDepth;
958   }
959 
slabInternal(P4 plane, boolean isDepth)960   public void slabInternal(P4 plane, boolean isDepth) {
961     //also from vwr
962     if (isDepth) {
963       depthPlane = plane;
964       depthPercentSetting = 0;
965     } else {
966       slabPlane = plane;
967       slabPercentSetting = 100;
968     }
969     slabDepthChanged();
970   }
971 
972   /**
973    * set internal slab or depth from screen-based slab or depth
974    *
975    * @param isDepth
976    */
setSlabDepthInternal(boolean isDepth)977   public void setSlabDepthInternal(boolean isDepth) {
978     if (isDepth)
979       depthPlane = null;
980     else
981       slabPlane = null;
982     finalizeTransformParameters();
983     slabInternal(getSlabDepthPlane(isDepth), isDepth);
984   }
985 
getSlabDepthPlane(boolean isDepth)986   private P4 getSlabDepthPlane(boolean isDepth) {
987     // the third row of the matrix defines the Z coordinate, which is all we need
988     // and, in fact, it defines the plane. How convenient!
989     // eval "slab set"
990     if (isDepth) {
991       if (depthPlane != null)
992         return depthPlane;
993     } else if (slabPlane != null) {
994         return slabPlane;
995     }
996     M4 m = matrixTransform;
997     P4 plane = P4.new4(-m.m20, -m.m21, -m.m22, -m.m23
998         + (isDepth ? depthValue : slabValue));
999     return plane;
1000   }
1001 
1002   /* ***************************************************************
1003    * PERSPECTIVE
1004    ****************************************************************/
1005 
1006   /* Jmol treatment of perspective   Bob Hanson 12/06
1007    *
1008    * See http://www.stolaf.edu/academics/chemapps/jmol/docs/misc/navigation.pdf
1009    *
1010 
1011 
1012    DEFAULT SCALE -- (zoom == 100)
1013 
1014    We start by defining a fixedRotationCenter and a modelRadius that encompasses
1015    the model. Then:
1016 
1017    defaultScalePixelsPerAngstrom = screenPixelCount / (2 * modelRadius)
1018 
1019    where:
1020 
1021    screenPixelCount is 2 less than the larger of height or width when zoomLarge == true
1022    and the smaller of the two when zoomLarge == false
1023 
1024    modelRadius is a rough estimate of the extent of the molecule.
1025    This pretty much makes a model span the window.
1026 
1027    This is applied as part of the matrixTransform.
1028 
1029    ADDING ZOOM
1030 
1031    For zoom, we just apply a zoom factor to the default scaling:
1032 
1033    scalePixelsPerAngstrom = zoom * defaultScalePixelsPerAngstrom
1034 
1035 
1036    ADDING PERSPECTIVE
1037 
1038    Imagine an old fashioned plate camera. The film surface is in front of the model
1039    some distance. Lines of perspective go from the plate (our screen) to infinity
1040    behind the model. We define:
1041 
1042    cameraDistance  -- the distance of the camera in pixels from the FRONT of the model.
1043 
1044    cameraDepth     -- a more scalable version of cameraDistance,
1045    measured in multiples of screenPixelCount.
1046 
1047    The atom position is transformed into screen-based coordinates as:
1048 
1049    Z = modelCenterOffset + atom.z * zoom * defaultScalePixelsPerAngstrom
1050 
1051    where
1052 
1053    modelCenterOffset = cameraDistance + screenPixelCount / 2
1054 
1055    Z is thus adjusted for zoom such that the center of the model stays in the same position.
1056    Defining the position of a vertical plane p as:
1057 
1058    p = (modelRadius + zoom * atom.z) / (2 * modelRadius)
1059 
1060    and using the definitions above, we have:
1061 
1062    Z = cameraDistance + screenPixelCount / 2
1063    + zoom * atom.z * screenPixelCount / (2 * modelRadius)
1064 
1065    or, more simply:
1066 
1067    Z = cameraDistance + p * screenPixelCount
1068 
1069    This will prove convenient for this discussion (but is never done in code).
1070 
1071    All perspective is, is the multiplication of the x and y coordinates by a scaling
1072    factor that depends upon this screen-based Z coordinate.
1073 
1074    We define:
1075 
1076    cameraScaleFactor = (cameraDepth + 0.5) / cameraDepth
1077    referencePlaneOffset = cameraDistance * cameraScaleFactor
1078    = (cameraDepth + 0.5) * screenPixelCount
1079 
1080    and the overall scaling as a function of distance from the camera is simply:
1081 
1082    f = perspectiveFactor = referencePlaneOffset / Z
1083 
1084    and thus using c for cameraDepth:
1085 
1086    f = (c + 0.5) * screenPixelCount / Z
1087    = (c + 0.5) * screenPixelCount / (c * screenPixelCount + p * screenPixelCount)
1088 
1089    and we simply have:
1090 
1091    f = (c + 0.5) / (c + p)
1092 
1093    Thus:
1094 
1095    when p = 0,   (front plane) the scale is cameraScaleFactor.
1096    when p = 0.5, (midplane) the scale is 1.
1097    when p = 1,   (rear plane) the scale is (cameraDepth + 0.5) / (cameraDepth + 1)
1098 
1099    as p approaches infinity, perspectiveFactor goes to 0;
1100    if p goes negative, we ignore it. Those points won't be rendered.
1101 
1102    GRAPHICAL INTERPRETATION
1103 
1104    The simplest way to see what is happening is to consider 1/f instead of f:
1105 
1106    1/f = (c + p) / (c + 0.5) = c / (c + 0.5) + p / (c + 0.5)
1107 
1108    This is a linear function of p, with 1/f=0 at p = -c, the camera position:
1109 
1110 
1111 
1112 
1113    \----------------0----------------/    midplane, p = 0.5, 1/f = 1
1114     \        model center           /     viewingRange = screenPixelCount
1115      \                             /
1116       \                           /
1117        \                         /
1118         \-----------------------/   front plane, p = 0, 1/f = c / (c + 0.5)
1119          \                     /    viewingRange = screenPixelCount / f
1120           \                   /
1121            \                 /
1122             \               /   The distance across is the distance that is viewable
1123              \             /    for this Z position. Just magnify a model and place its
1124   ^            \           /     center at 0. Whatever part of the model is within the
1125   |             \         /      triangle will be viewed, scaling each distance so that
1126   Z increasing    \       /       it ends up screenWidthPixels wide.
1127   |               \     /
1128   |                \   /
1129                    \ /
1130   Z = 0              X  camera position, p = -c, 1/f = 0
1131                        viewingRange = 0
1132 
1133    VISUAL RANGE
1134 
1135    We simply define a fixed visual range that can be seen by the observer.
1136    That range is set at the referencePlaneOffset. Any point ahead of this plane is not shown.
1137 
1138    VERSION 10
1139 
1140    In Jmol 10.2 there was a much more complicated formula for perspectiveFactor, namely
1141    (where "c" is the cameraDepth):
1142 
1143    cameraScaleFactor(old) = 1 + 0.5 / c + 0.02
1144    z = cameraDistance + (modelRadius + z0) * scalePixelsPerAngstrom * cameraScaleFactor * zoom
1145 
1146    Note that the zoom was being applied in such a way that changing the zoom also changed the
1147    model midplane position and that the camera scaling factor was being applied in the
1148    matrix transformation. This lead to a very complicated but subtle error in perspective.
1149 
1150    This error was noticed by Charles Xie and amounts to only a few percent for the
1151    cameraDepth that was fixed at 3 in Jmol 10.0. The error was 0 at the front of the model,
1152    2% at the middle, and 3.5% at the back, roughly.
1153 
1154    Fixing this error now allows us to adjust cameraDepth at will and to do proper navigation.
1155 
1156    */
1157 
1158   public boolean perspectiveDepth = true;
1159   protected boolean scale3D = false;
1160   protected float cameraDepth = 3f;
1161   protected float cameraDepthSetting = 3f;
1162   public float visualRangeAngstroms; // set in stateManager to 5f;
1163   public float cameraDistance = 1000f; // prevent divide by zero on startup
1164 
1165   /**
1166    * This method returns data needed by the VRML, X3D, and IDTF/U3D exporters.
1167    * It also should serve as a valuable resource for anyone adapting Jmol and
1168    * wanting to know how the Jmol 11+ camera business works.
1169    * @return a set of camera data
1170    */
getCameraFactors()1171   public P3[] getCameraFactors() {
1172     aperatureAngle = (float) (Math.atan2(screenPixelCount / 2f,
1173         referencePlaneOffset) * 2 * 180 / Math.PI);
1174     cameraDistanceFromCenter = referencePlaneOffset / scalePixelsPerAngstrom;
1175 
1176     P3 ptRef = P3.new3(screenWidth / 2, screenHeight / 2, referencePlaneOffset);
1177     unTransformPoint(ptRef, ptRef);
1178 
1179     // NOTE: Camera position will be approximate.
1180     // when the model has been shifted with CTRL-ALT
1181     // the center of distortion is not the screen center.
1182     // The simpler perspective model in VRML and U3D
1183     // doesn't allow for that. (of course, one could argue,
1184     // that's because they are more REALISTIC). We do it
1185     // this way so that visual metrics in the model are preserved
1186     // when the model is shifted using CTRL-ALT, and it was found
1187     // that if you didn't do that, moving the model was very odd
1188     // in that a fish-eye distortion was present as you moved it.
1189 
1190     // note that navigation mode should be EXACTLY reproduced
1191     // in these renderers.
1192 
1193     P3 ptCamera = P3.new3(screenWidth / 2, screenHeight / 2, 0);
1194     unTransformPoint(ptCamera, ptCamera);
1195     ptCamera.sub(fixedRotationCenter);
1196     P3 pt = P3.new3(screenWidth / 2, screenHeight / 2, cameraDistanceFromCenter
1197         * scalePixelsPerAngstrom);
1198     unTransformPoint(pt, pt);
1199     pt.sub(fixedRotationCenter);
1200     ptCamera.add(pt);
1201 
1202     //        System.out.println("TM no " + navigationOffset + " rpo "
1203     //            + referencePlaneOffset + " aa " + aperatureAngle + " sppa "
1204     //            + scalePixelsPerAngstrom + " vr " + visualRange + " sw/vr "
1205     //            + screenWidth / visualRange + " " + ptRef + " " + fixedRotationCenter);
1206 
1207     return new P3[] {
1208         ptRef,
1209         ptCamera,
1210         fixedRotationCenter,
1211         P3.new3(cameraDistanceFromCenter, aperatureAngle,
1212             scalePixelsPerAngstrom) };
1213   }
1214 
setPerspectiveDepth(boolean perspectiveDepth)1215   void setPerspectiveDepth(boolean perspectiveDepth) {
1216     if (this.perspectiveDepth == perspectiveDepth)
1217       return;
1218     this.perspectiveDepth = perspectiveDepth;
1219     vwr.g.setB("perspectiveDepth", perspectiveDepth);
1220     resetFitToScreen(false);
1221   }
1222 
getPerspectiveDepth()1223   public boolean getPerspectiveDepth() {
1224     return perspectiveDepth;
1225   }
1226 
1227   /**
1228    * either as a percent -300, or as a float 3.0 note this percent is of
1229    * zoom=100 size of model
1230    *
1231    * @param percent
1232    * @param resetSlab
1233    */
setCameraDepthPercent(float percent, boolean resetSlab)1234   public void setCameraDepthPercent(float percent, boolean resetSlab) {
1235     resetNavigationPoint(resetSlab);
1236     float screenMultiples = (percent < 0 ? -percent / 100 : percent);
1237     if (screenMultiples == 0)
1238       return;
1239     cameraDepthSetting = screenMultiples;
1240     vwr.g.setF("cameraDepth", cameraDepthSetting);
1241     //if (mode == MODE_NAVIGATION)// don't remember why we would do that...
1242     cameraDepth = Float.NaN;
1243   }
1244 
getCameraDepth()1245   public float getCameraDepth() {
1246     return cameraDepthSetting;
1247   }
1248 
1249 
1250   //  M4 getUnscaledTransformMatrix() {
1251   //    //for povray only
1252   //    M4 unscaled = M4.newM4(null);
1253   //    vectorTemp.setT(fixedRotationCenter);
1254   //    matrixTemp.setZero();
1255   //    matrixTemp.setTranslation(vectorTemp);
1256   //    unscaled.sub(matrixTemp);
1257   //    matrixTemp.setToM3(matrixRotate);
1258   //    unscaled.mul2(matrixTemp, unscaled);
1259   //    return unscaled;
1260   //  }
1261 
1262   /* ***************************************************************
1263    * SCREEN SCALING
1264    ****************************************************************/
1265   public int width;
1266 
1267   public int height;
1268   public int screenPixelCount;
1269   float scalePixelsPerAngstrom;
1270   public float scaleDefaultPixelsPerAngstrom;
1271   float scale3DAngstromsPerInch;
1272   protected boolean antialias;
1273   private boolean useZoomLarge, zoomHeight;
1274 
1275   int screenWidth, screenHeight;
1276 
setScreenParameters0(int screenWidth, int screenHeight, boolean useZoomLarge, boolean antialias, boolean resetSlab, boolean resetZoom)1277   private void setScreenParameters0(int screenWidth, int screenHeight,
1278                                     boolean useZoomLarge, boolean antialias,
1279                                     boolean resetSlab, boolean resetZoom) {
1280     if (screenWidth == Integer.MAX_VALUE)
1281       return;
1282     this.screenWidth = screenWidth;
1283     this.screenHeight = screenHeight;
1284     this.useZoomLarge = useZoomLarge;
1285     this.antialias = antialias;
1286     width = (antialias ? screenWidth * 2 : screenWidth);
1287     height = (antialias ? screenHeight * 2 : screenHeight);
1288     scaleFitToScreen(false, useZoomLarge, resetSlab, resetZoom);
1289   }
1290 
setAntialias(boolean TF)1291   void setAntialias(boolean TF) {
1292     boolean isNew = (antialias != TF);
1293     antialias = TF;
1294     width = (antialias ? screenWidth * 2 : screenWidth);
1295     height = (antialias ? screenHeight * 2 : screenHeight);
1296     if (isNew)
1297       scaleFitToScreen(false, useZoomLarge, false, false);
1298   }
1299 
defaultScaleToScreen(float radius)1300   public float defaultScaleToScreen(float radius) {
1301     /*
1302      *
1303      * the presumption here is that the rotation center is at pixel
1304      * (150,150) of a 300x300 window. modelRadius is
1305      * a rough estimate of the furthest distance from the center of rotation
1306      * (but not including pmesh, special lines, planes, etc. -- just atoms)
1307      *
1308      * also that we do not want it to be possible for the model to rotate
1309      * out of bounds of the applet. For internal spinning I had to turn
1310      * of any calculation that would change the rotation radius.  hansonr
1311      *
1312      */
1313     return screenPixelCount / 2f / radius;
1314   }
1315 
resetFitToScreen(boolean andCenter)1316   private void resetFitToScreen(boolean andCenter) {
1317     scaleFitToScreen(andCenter, vwr.g.zoomLarge, true, true);
1318   }
1319 
scaleFitToScreen(boolean andCenter, boolean zoomLarge, boolean resetSlab, boolean resetZoom)1320   void scaleFitToScreen(boolean andCenter, boolean zoomLarge,
1321                         boolean resetSlab, boolean resetZoom) {
1322     if (width == 0 || height == 0) {
1323       screenPixelCount = 1;
1324     } else {
1325 
1326       // translate to the middle of the screen
1327       fixedTranslation.set(width * (andCenter ? 0.5f : xTranslationFraction),
1328           height * (andCenter ? 0.5f : yTranslationFraction), 0);
1329       setTranslationFractions();
1330       if (andCenter)
1331         camera.set(0, 0, 0);
1332       if (resetZoom)
1333         resetNavigationPoint(resetSlab);
1334       // 2005 02 22
1335       // switch to finding larger screen dimension
1336       // find smaller screen dimension
1337       if (zoomHeight)
1338         zoomLarge = (height > width);
1339       screenPixelCount = (zoomLarge == (height > width) ? height : width);
1340       //screenPixelCount = Math.min(height, width, arg1);
1341     }
1342     // ensure that rotations don't leave some atoms off the screen
1343     // note that this radius is to the furthest outside edge of an atom
1344     // given the current VDW radius setting. it is currently *not*
1345     // recalculated when the vdw radius settings are changed
1346     // leave a very small margin - only 1 on top and 1 on bottom
1347     if (screenPixelCount > 2)
1348       screenPixelCount -= 2;
1349     scaleDefaultPixelsPerAngstrom = defaultScaleToScreen(modelRadius);
1350   }
1351 
scaleToScreen(int z, int milliAngstroms)1352   public float scaleToScreen(int z, int milliAngstroms) {
1353     if (milliAngstroms == 0 || z < 2)
1354       return 0;
1355     float pixelSize = scaleToPerspective(z, milliAngstroms
1356         * scalePixelsPerAngstrom / 1000);
1357     return (pixelSize > 0 ? pixelSize : 1);
1358   }
1359 
unscaleToScreen(float z, float screenDistance)1360   public float unscaleToScreen(float z, float screenDistance) {
1361     float d = screenDistance / scalePixelsPerAngstrom;
1362     return (perspectiveDepth ? d / getPerspectiveFactor(z) : d);
1363   }
1364 
scaleToPerspective(int z, float sizeAngstroms)1365   public float scaleToPerspective(int z, float sizeAngstroms) {
1366     //DotsRenderer only
1367     //old: return (perspectiveDepth ? sizeAngstroms * perspectiveFactor(z)
1368     //: sizeAngstroms);
1369 
1370     return (perspectiveDepth ? sizeAngstroms * getPerspectiveFactor(z)
1371         : sizeAngstroms);
1372 
1373   }
1374 
1375   /* ***************************************************************
1376    * TRANSFORMATIONS
1377    ****************************************************************/
1378 
1379   public final M4 matrixTransform = new M4();
1380   public final M4 matrixTransformInv = new M4();
1381 
1382    protected final P3 fScrPt = new P3();
1383   protected final P3i iScrPt = new P3i();
1384 
1385   final Point3fi ptVibTemp = new Point3fi();
1386 
1387   public boolean navigating = false;
1388   public int mode = MODE_STANDARD;
1389   public int defaultMode = MODE_STANDARD;
1390 
setNavigationMode(boolean TF)1391   void setNavigationMode(boolean TF) {
1392     mode = (TF ? MODE_NAVIGATION : defaultMode);
1393     resetNavigationPoint(true);
1394   }
1395 
isNavigating()1396   public boolean isNavigating() {
1397     return navigating || navOn;
1398   }
1399 
finalizeTransformParameters()1400   public synchronized void finalizeTransformParameters() {
1401     haveNotifiedNaN = false;
1402     fixedRotationOffset.setT(fixedTranslation);
1403     camera.setT(cameraSetting);
1404     internalSlab = slabEnabled && (slabPlane != null || depthPlane != null);
1405     float newZoom = getZoomSetting();
1406     if (zmPct != newZoom) {
1407       zmPct = newZoom;
1408       if (!vwr.g.fontCaching)
1409         vwr.gdata.clearFontCache();
1410     }
1411     calcCameraFactors();
1412     calcTransformMatrix();
1413     if (mode == MODE_NAVIGATION)
1414       calcNavigationPoint();
1415     else
1416       calcSlabAndDepthValues();
1417   }
1418 
getZoomSetting()1419   public float getZoomSetting() {
1420     if (zmPctSet < 5)
1421       zmPctSet = 5;
1422     if (zmPctSet > MAXIMUM_ZOOM_PERCENTAGE)
1423       zmPctSet = MAXIMUM_ZOOM_PERCENTAGE;
1424     return (zoomEnabled || mode == MODE_NAVIGATION ? zmPctSet : 100);
1425   }
1426 
1427   /**
1428    * sets slab and depth, possibly using visual range considerations for setting
1429    * the slab-clipping plane. (slab on; slab 0)
1430    *
1431    * superceded in navigation mode
1432    *
1433    */
1434 
calcSlabAndDepthValues()1435   public void calcSlabAndDepthValues() {
1436     if (slabRange < 1)
1437       slabValue = zValueFromPercent(slabPercentSetting);
1438     else
1439       slabValue = (int) Math.floor(modelCenterOffset * slabRange
1440           / (2 * modelRadius) * (zmPctSet / 100));
1441     depthValue = zValueFromPercent(depthPercentSetting);
1442     if (zSlabPercentSetting == zDepthPercentSetting) {
1443       zSlabValue = slabValue;
1444       zDepthValue = depthValue;
1445     } else {
1446       zSlabValue = zValueFromPercent(zSlabPercentSetting);
1447       zDepthValue = zValueFromPercent(zDepthPercentSetting);
1448     }
1449     if (zSlabPoint != null) {
1450       try {
1451         transformPt3f(zSlabPoint, pointT2);
1452         zSlabValue = (int) pointT2.z;
1453       } catch (Exception e) {
1454         // don't care
1455       }
1456     }
1457     vwr.g.setO("_slabPlane", Escape.eP4(getSlabDepthPlane(false)));
1458     vwr.g.setO("_depthPlane", Escape.eP4(getSlabDepthPlane(true)));
1459     if (slabEnabled)
1460       return;
1461     slabValue = 0;
1462     depthValue = Integer.MAX_VALUE;
1463   }
1464 
zValueFromPercent(int zPercent)1465   public int zValueFromPercent(int zPercent) {
1466     return (int) Math.floor((1 - zPercent / 50f) * modelRadiusPixels
1467         + modelCenterOffset);
1468   }
1469 
calcTransformMatrix()1470   public synchronized void calcTransformMatrix() {
1471 
1472     matrixTransform.setIdentity();
1473 
1474     // first, translate the coordinates back to the center
1475 
1476     vectorTemp.sub2(frameOffset, fixedRotationCenter);
1477     matrixTransform.setTranslation(vectorTemp);
1478 
1479     // multiply by angular rotations
1480     // this is *not* the same as  matrixTransform.mul(matrixRotate);
1481     matrixTemp.setToM3(stereoFrame ? matrixStereo : matrixRotate);
1482     matrixTransform.mul2(matrixTemp, matrixTransform);
1483     // scale to screen coordinates
1484     matrixTemp.setIdentity();
1485     matrixTemp.m00 = matrixTemp.m11 = matrixTemp.m22 = scalePixelsPerAngstrom;
1486     // negate y (for screen) and z (for zbuf)
1487     matrixTemp.m11 = matrixTemp.m22 = -scalePixelsPerAngstrom;
1488 
1489     matrixTransform.mul2(matrixTemp, matrixTransform);
1490     //z-translate to set rotation center at midplane (Nav) or front plane (V10)
1491     matrixTransform.m23 += modelCenterOffset;
1492     try {
1493       matrixTransformInv.setM4(matrixTransform).invert();
1494     } catch (Exception e) {
1495       System.out.println("ERROR INVERTING matrixTransform!");
1496       // ignore -- this is a Mac issue on applet startup
1497     }
1498     // note that the image is still centered at 0, 0 in the xy plane
1499 
1500     //System.out.println("TM matrixTransform " + matrixTransform);
1501   }
1502 
rotatePoint(T3 pt, T3 ptRot)1503   public void rotatePoint(T3 pt, T3 ptRot) {
1504     matrixRotate.rotate2(pt, ptRot);
1505     ptRot.y = -ptRot.y;
1506   }
1507 
getScreenTemp(T3 ptXYZ)1508   protected void getScreenTemp(T3 ptXYZ) {
1509     matrixTransform.rotTrans2(ptXYZ, fScrPt);
1510   }
1511 
transformPtScr(T3 ptXYZ, P3i pointScreen)1512   public void transformPtScr(T3 ptXYZ, P3i pointScreen) {
1513     pointScreen.setT(transformPt(ptXYZ));
1514   }
1515 
transformPtScrT3(T3 ptXYZ, T3 pointScreen)1516   public void transformPtScrT3(T3 ptXYZ, T3 pointScreen) {
1517     transformPt(ptXYZ);
1518     // note that this point may be returned as z=1 if the point is
1519     // past the camera or slabbed internally
1520     pointScreen.setT(fScrPt);
1521   }
1522 
transformPt3f(T3 ptXYZ, P3 screen)1523   public void transformPt3f(T3 ptXYZ, P3 screen) {
1524     applyPerspective(ptXYZ, ptXYZ);
1525     screen.setT(fScrPt);
1526   }
1527 
transformPtNoClip(T3 ptXYZ, T3 pointScreen)1528   public void transformPtNoClip(T3 ptXYZ, T3 pointScreen) {
1529     applyPerspective(ptXYZ, null);
1530     pointScreen.setT(fScrPt);
1531   }
1532 
1533   /**
1534    * CAUTION! returns a POINTER TO A TEMPORARY VARIABLE
1535    *
1536    * @param ptXYZ
1537    * @return POINTER TO point3iScreenTemp
1538    */
transformPt(T3 ptXYZ)1539   public synchronized P3i transformPt(T3 ptXYZ) {
1540     return applyPerspective(ptXYZ, internalSlab ? ptXYZ : null);
1541   }
1542 
1543   /**
1544    * @param ptXYZ
1545    * @param v
1546    * @return POINTER TO TEMPORARY VARIABLE (caution!) point3iScreenTemp
1547    */
transformPtVib(P3 ptXYZ, Vibration v)1548   public P3i transformPtVib(P3 ptXYZ, Vibration v) {
1549     ptVibTemp.setT(ptXYZ);
1550     return applyPerspective(getVibrationPoint(v, ptVibTemp, Float.NaN), ptXYZ);
1551   }
1552 
1553   /**
1554    * return
1555    * @param v
1556    * @param pt temporary value; also returned
1557    * @param scale
1558    * @return pt
1559    */
getVibrationPoint(Vibration v, T3 pt, float scale)1560   public T3 getVibrationPoint(Vibration v, T3 pt, float scale) {
1561     return v.setCalcPoint(pt, vibrationT,
1562         (Float.isNaN(scale) ? vibrationScale : scale), vwr.g.modulationScale);
1563   }
1564 
transformPt2D(T3 ptXyp)1565   public synchronized P3i transformPt2D(T3 ptXyp) {
1566     // axes position [50 50]
1567     // just does the processing for [x y] and [x y %]
1568     if (ptXyp.z == -Float.MAX_VALUE) {
1569       iScrPt.x = (int) Math.floor(ptXyp.x / 100 * screenWidth);
1570       iScrPt.y = (int) Math
1571           .floor((1 - ptXyp.y / 100) * screenHeight);
1572     } else {
1573       iScrPt.x = (int) ptXyp.x;
1574       iScrPt.y = (screenHeight - (int) ptXyp.y);
1575     }
1576     if (antialias) {
1577       iScrPt.x <<= 1;
1578       iScrPt.y <<= 1;
1579     }
1580     matrixTransform.rotTrans2(fixedRotationCenter, fScrPt);
1581     iScrPt.z = (int) fScrPt.z;
1582     return iScrPt;
1583   }
1584 
1585   /**
1586    * adjusts the temporary point for perspective and offsets
1587    *
1588    * @param ptXYZ
1589    * @param ptRef
1590    * @return temporary point!!!
1591    *
1592    */
applyPerspective(T3 ptXYZ, T3 ptRef)1593   private P3i applyPerspective(T3 ptXYZ, T3 ptRef) {
1594 
1595     getScreenTemp(ptXYZ);
1596     //System.out.println(point3fScreenTemp);
1597 
1598     // fixedRotation point is at the origin initially
1599 
1600     float z = fScrPt.z;
1601 
1602     // this could easily go negative -- behind the screen --
1603     // but we don't care. In fact, that just makes it easier,
1604     // because it means we won't render it.
1605     // we should probably assign z = 0 as "unrenderable"
1606 
1607     if (Float.isNaN(z)) {
1608       if (!haveNotifiedNaN && Logger.debugging)
1609         Logger.debug("NaN seen in TransformPoint");
1610       haveNotifiedNaN = true;
1611       z = fScrPt.z = 1;
1612     } else if (z <= 0) {
1613       // just don't let z go past 1 BH 11/15/06
1614       z = fScrPt.z = 1;
1615     }
1616 
1617     // x and y are moved inward (generally) relative to 0, which
1618     // is either the fixed rotation center or the navigation center
1619 
1620     // at this point coordinates are centered on rotation center
1621 
1622     switch (mode) {
1623     case MODE_NAVIGATION:
1624       // move nav center to 0; refOffset = Nav - Rot
1625       fScrPt.x -= navigationShiftXY.x;
1626       fScrPt.y -= navigationShiftXY.y;
1627       break;
1628     case MODE_PERSPECTIVE_PYMOL:
1629       fScrPt.x += perspectiveShiftXY.x;
1630       fScrPt.y += perspectiveShiftXY.y;
1631       break;
1632     }
1633     if (perspectiveDepth) {
1634       // apply perspective factor
1635       float factor = getPerspectiveFactor(z);
1636       fScrPt.x *= factor;
1637       fScrPt.y *= factor;
1638     }
1639     switch (mode) {
1640     case MODE_NAVIGATION:
1641       fScrPt.x += navigationOffset.x;
1642       fScrPt.y += navigationOffset.y;
1643       break;
1644     case MODE_PERSPECTIVE_PYMOL:
1645       fScrPt.x -= perspectiveShiftXY.x;
1646       fScrPt.y -= perspectiveShiftXY.y;
1647       //$FALL-THROUGH$
1648     case MODE_STANDARD:
1649       fScrPt.x += fixedRotationOffset.x;
1650       fScrPt.y += fixedRotationOffset.y;
1651       break;
1652     }
1653     if (Float.isNaN(fScrPt.x) && !haveNotifiedNaN) {
1654       if (Logger.debugging)
1655         Logger.debug("NaN found in transformPoint ");
1656       haveNotifiedNaN = true;
1657     }
1658 
1659     iScrPt.set((int) fScrPt.x, (int) fScrPt.y,
1660         (int) fScrPt.z);
1661 
1662     if (ptRef != null && xyzIsSlabbedInternal(ptRef))
1663       fScrPt.z = iScrPt.z = 1;
1664     return iScrPt;
1665   }
1666 
xyzIsSlabbedInternal(T3 ptRef)1667   public boolean xyzIsSlabbedInternal(T3 ptRef) {
1668     return (slabPlane != null
1669         && ptRef.x * slabPlane.x + ptRef.y * slabPlane.y + ptRef.z
1670             * slabPlane.z + slabPlane.w > 0 || depthPlane != null
1671         && ptRef.x * depthPlane.x + ptRef.y * depthPlane.y + ptRef.z
1672             * depthPlane.z + depthPlane.w < 0);
1673   }
1674 
1675   final protected P3 untransformedPoint = new P3();
1676 
1677   /* ***************************************************************
1678    * move/moveTo support
1679    ****************************************************************/
1680 
move(JmolScriptEvaluator eval, V3 dRot, float dZoom, V3 dTrans, float dSlab, float floatSecondsTotal, int fps)1681   void move(JmolScriptEvaluator eval, V3 dRot, float dZoom, V3 dTrans,
1682             float dSlab, float floatSecondsTotal, int fps) {
1683 
1684     movetoThread = (JmolThread) Interface.getOption("thread.MoveToThread", vwr,
1685         "tm");
1686     movetoThread.setManager(this, vwr, new Object[] { dRot, dTrans,
1687         new float[] { dZoom, dSlab, floatSecondsTotal, fps } });
1688     if (floatSecondsTotal > 0)
1689       movetoThread.setEval(eval);
1690     movetoThread.run();
1691   }
1692 
1693   protected final P3 ptTest1 = new P3();
1694   protected final P3 ptTest2 = new P3();
1695   protected final P3 ptTest3 = new P3();
1696   protected final A4 aaTest1 = new A4();
1697   protected final M3 matrixTest = new M3();
1698 
isInPosition(V3 axis, float degrees)1699   public boolean isInPosition(V3 axis, float degrees) {
1700     if (Float.isNaN(degrees))
1701       return true;
1702     aaTest1.setVA(axis, (float) (degrees / degreesPerRadian));
1703     ptTest1.set(4.321f, 1.23456f, 3.14159f);
1704     getRotation(matrixTest);
1705     matrixTest.rotate2(ptTest1, ptTest2);
1706     matrixTest.setAA(aaTest1).rotate2(ptTest1, ptTest3);
1707     return (ptTest3.distance(ptTest2) < 0.1);
1708   }
1709 
moveToPyMOL(JmolScriptEvaluator eval, float floatSecondsTotal, float[] pymolView)1710   public boolean moveToPyMOL(JmolScriptEvaluator eval, float floatSecondsTotal,
1711                              float[] pymolView) {
1712     // PyMOL matrices are inverted (row-based)
1713     M3 m3 = M3.newA9(pymolView);
1714     m3.invert();
1715     float cameraX = pymolView[9];
1716     float cameraY = -pymolView[10];
1717     float pymolDistanceToCenter = -pymolView[11];
1718     P3 center = P3.new3(pymolView[12], pymolView[13], pymolView[14]);
1719     float pymolDistanceToSlab = pymolView[15]; // <=0 to ignore
1720     float pymolDistanceToDepth = pymolView[16];
1721     float fov = pymolView[17];
1722     boolean isOrtho = (fov >= 0);
1723     setPerspectiveDepth(!isOrtho);
1724 
1725     // note that set zoomHeight is required for proper zooming
1726 
1727     // calculate Jmol camera position, which is in screen widths,
1728     // and is from the front of the screen, not the center.
1729     //
1730     //               |--screen height--| 1 unit
1731     //                       |-rotrad -|
1732     //                       o        /
1733     //                       |       /
1734     //                       |theta /
1735     //                       |     /
1736     // pymolDistanceToCenter |    /
1737     //                       |   /
1738     //                       |  /
1739     //                       | / theta = fov/2
1740     //                       |/
1741     //
1742 
1743     // we convert fov to rotation radius
1744     float theta = Math.abs(fov) / 2;
1745     float tan = (float) Math.tan(theta * Math.PI / 180);
1746     float rotationRadius = pymolDistanceToCenter * tan;
1747 
1748     // Jmol camera units are fraction of screen size (height in this case)
1749     float jmolCameraToCenter = 0.5f / tan;
1750     float cameraDepth = jmolCameraToCenter - 0.5f;
1751 
1752     // other units are percent; this factor is 100% / (2*rotationRadius)
1753     float f = 50 / rotationRadius;
1754 
1755     if (pymolDistanceToSlab > 0) {
1756       int slab = 50 + (int) ((pymolDistanceToCenter - pymolDistanceToSlab) * f);
1757       int depth = 50 + (int) ((pymolDistanceToCenter - pymolDistanceToDepth) * f);
1758       // could animate these? Does PyMOL?
1759       setSlabEnabled(true);
1760       slabToPercent(slab);
1761       depthToPercent(depth);
1762       if (pymolView.length == 21) {
1763         // from PSE file load only --
1764         boolean depthCue = (pymolView[18] != 0);
1765         boolean fog = (pymolView[19] != 0);
1766         float fogStart = pymolView[20];
1767         // conversion to Jmol zShade, zSlab, zDepth
1768         setZShadeEnabled(depthCue);
1769         if (depthCue) {
1770           if (fog) {
1771             vwr.setIntProperty("zSlab",
1772                 (int) Math.min(100, slab + fogStart * (depth - slab)));
1773           } else {
1774             vwr.setIntProperty("zSlab", (int) ((slab + depth) / 2f));
1775           }
1776           vwr.setIntProperty("zDepth", depth);
1777         }
1778       }
1779     }
1780     moveTo(eval, floatSecondsTotal, center, null, 0, m3, 100, Float.NaN,
1781         Float.NaN, rotationRadius, null, Float.NaN, Float.NaN, Float.NaN,
1782         cameraDepth, cameraX, cameraY);
1783     return true;
1784   }
1785 
1786   // from Viewer
moveTo(JmolScriptEvaluator eval, float floatSecondsTotal, P3 center, T3 rotAxis, float degrees, M3 matrixEnd, float zoom, float xTrans, float yTrans, float newRotationRadius, P3 navCenter, float xNav, float yNav, float navDepth, float cameraDepth, float cameraX, float cameraY)1787   void moveTo(JmolScriptEvaluator eval, float floatSecondsTotal, P3 center,
1788               T3 rotAxis, float degrees, M3 matrixEnd, float zoom,
1789               float xTrans, float yTrans, float newRotationRadius,
1790               P3 navCenter, float xNav, float yNav, float navDepth,
1791               float cameraDepth, float cameraX, float cameraY) {
1792     if (matrixEnd == null) {
1793       matrixEnd = new M3();
1794       V3 axis = V3.newV(rotAxis);
1795       if (Float.isNaN(degrees)) {
1796         matrixEnd.m00 = Float.NaN;
1797       } else if (degrees < 0.01f && degrees > -0.01f) {
1798         // getRotation(matrixEnd);
1799         matrixEnd.setScale(1);
1800       } else {
1801         if (axis.x == 0 && axis.y == 0 && axis.z == 0) {
1802           // invalid ... no rotation
1803           /*
1804            * why were we then sleeping? int sleepTime = (int) (floatSecondsTotal
1805            * * 1000) - 30; if (sleepTime > 0) { try { Thread.sleep(sleepTime); }
1806            * catch (InterruptedException ie) { } }
1807            */
1808           return;
1809         }
1810         A4 aaMoveTo = new A4();
1811         aaMoveTo.setVA(axis, (float) (degrees / degreesPerRadian));
1812         matrixEnd.setAA(aaMoveTo);
1813       }
1814     }
1815     if (cameraX == cameraSetting.x)
1816       cameraX = Float.NaN;
1817     if (cameraY == cameraSetting.y)
1818       cameraY = Float.NaN;
1819     if (cameraDepth == this.cameraDepth)
1820       cameraDepth = Float.NaN;
1821     if (!Float.isNaN(cameraX))
1822       xTrans = cameraX * 50 / newRotationRadius / width * screenPixelCount;
1823     if (!Float.isNaN(cameraY))
1824       yTrans = cameraY * 50 / newRotationRadius / height * screenPixelCount;
1825     float pixelScale = (center == null ? scaleDefaultPixelsPerAngstrom
1826         : defaultScaleToScreen(newRotationRadius));
1827     if (floatSecondsTotal <= 0) {
1828       setAll(center, matrixEnd, navCenter, zoom, xTrans, yTrans,
1829           newRotationRadius, pixelScale, navDepth, xNav, yNav, cameraDepth,
1830           cameraX, cameraY);
1831       vwr.moveUpdate(floatSecondsTotal);
1832       vwr.finalizeTransformParameters();
1833       return;
1834     }
1835 
1836     try {
1837       if (movetoThread == null)
1838         movetoThread = (JmolThread) Interface.getOption("thread.MoveToThread",
1839             vwr, "tm");
1840       int nSteps = movetoThread.setManager(this, vwr, new Object[] {
1841           center,
1842           matrixEnd,
1843           navCenter,
1844           new float[] { floatSecondsTotal, zoom, xTrans, yTrans,
1845               newRotationRadius, pixelScale, navDepth, xNav, yNav, cameraDepth,
1846               cameraX, cameraY } });
1847       if (nSteps <= 0 || vwr.g.waitForMoveTo) {
1848         if (nSteps > 0)
1849           movetoThread.setEval(eval);
1850         movetoThread.run();
1851         if (!vwr.isSingleThreaded)
1852           movetoThread = null;
1853       } else {
1854         movetoThread.start();
1855       }
1856     } catch (Exception e) {
1857       // ignore
1858     }
1859   }
1860 
setAll(P3 center, M3 m, P3 navCenter, float zoom, float xTrans, float yTrans, float rotationRadius, float pixelScale, float navDepth, float xNav, float yNav, float cameraDepth, float cameraX, float cameraY)1861   public void setAll(P3 center, M3 m, P3 navCenter, float zoom, float xTrans,
1862                      float yTrans, float rotationRadius, float pixelScale,
1863                      float navDepth, float xNav, float yNav, float cameraDepth,
1864                      float cameraX, float cameraY) {
1865     if (!Float.isNaN(m.m00))
1866       setRotation(m);
1867     if (center != null)
1868       moveRotationCenter(center, !windowCentered);
1869     if (navCenter != null && mode == MODE_NAVIGATION)
1870       navigationCenter.setT(navCenter);
1871     if (!Float.isNaN(cameraDepth))
1872       setCameraDepthPercent(cameraDepth, false);
1873     if (!Float.isNaN(cameraX) && !Float.isNaN(cameraY))
1874       setCamera(cameraX, cameraY);
1875     if (!Float.isNaN(zoom))
1876       zoomToPercent(zoom);
1877     if (!Float.isNaN(rotationRadius))
1878       modelRadius = rotationRadius;
1879     if (!Float.isNaN(pixelScale))
1880       scaleDefaultPixelsPerAngstrom = pixelScale;
1881     if (!Float.isNaN(xTrans) && !Float.isNaN(yTrans)) {
1882       translateToPercent('x', xTrans);
1883       translateToPercent('y', yTrans);
1884     }
1885 
1886     if (mode == MODE_NAVIGATION) {
1887       if (!Float.isNaN(xNav) && !Float.isNaN(yNav))
1888         navTranslatePercentOrTo(0, xNav, yNav);
1889       if (!Float.isNaN(navDepth))
1890         setNavigationDepthPercent(navDepth);
1891     }
1892   }
1893 
stopMotion()1894   public void stopMotion() {
1895     movetoThread = null;
1896     //setSpinOff();// trouble here with Viewer.checkHalt
1897   }
1898 
getRotationText()1899   String getRotationText() {
1900     axisangleT.setM(matrixRotate);
1901     float degrees = (float) (axisangleT.angle * degreesPerRadian);
1902     SB sb = new SB();
1903     vectorT.set(axisangleT.x, axisangleT.y, axisangleT.z);
1904     if (degrees < 0.01f)
1905       return "{0 0 1 0}";
1906     vectorT.normalize();
1907     vectorT.scale(1000);
1908     sb.append("{");
1909     truncate0(sb, vectorT.x);
1910     truncate0(sb, vectorT.y);
1911     truncate0(sb, vectorT.z);
1912     truncate2(sb, degrees);
1913     sb.append("}");
1914     return sb.toString();
1915   }
1916 
getMoveToText(float timespan, boolean addComments)1917   public String getMoveToText(float timespan, boolean addComments) {
1918     finalizeTransformParameters();
1919     SB sb = new SB();
1920     sb.append("moveto ");
1921     if (addComments)
1922       sb.append("/* time, axisAngle */ ");
1923     sb.appendF(timespan);
1924     sb.append(" ").append(getRotationText());
1925     if (addComments)
1926       sb.append(" /* zoom, translation */ ");
1927     truncate2(sb, zmPctSet);
1928     truncate2(sb, getTranslationXPercent());
1929     truncate2(sb, getTranslationYPercent());
1930     sb.append(" ");
1931     if (addComments)
1932       sb.append(" /* center, rotationRadius */ ");
1933     sb.append(getCenterText());
1934     sb.append(" ").appendF(modelRadius);
1935     sb.append(getNavigationText(addComments));
1936     if (addComments)
1937       sb.append(" /* cameraDepth, cameraX, cameraY */ ");
1938     truncate2(sb, cameraDepth);
1939     truncate2(sb, cameraSetting.x);
1940     truncate2(sb, cameraSetting.y);
1941     sb.append(";");
1942     return sb.toString();
1943   }
1944 
getCenterText()1945   private String getCenterText() {
1946     return Escape.eP(fixedRotationCenter);
1947   }
1948 
getRotateXyzText()1949   private String getRotateXyzText() {
1950     SB sb = new SB();
1951     float m20 = matrixRotate.m20;
1952     float rY = -(float) (Math.asin(m20) * degreesPerRadian);
1953     float rX, rZ;
1954     if (m20 > .999f || m20 < -.999f) {
1955       rX = -(float) (Math.atan2(matrixRotate.m12, matrixRotate.m11) * degreesPerRadian);
1956       rZ = 0;
1957     } else {
1958       rX = (float) (Math.atan2(matrixRotate.m21, matrixRotate.m22) * degreesPerRadian);
1959       rZ = (float) (Math.atan2(matrixRotate.m10, matrixRotate.m00) * degreesPerRadian);
1960     }
1961     sb.append("reset");
1962     sb.append(";center ").append(getCenterText());
1963     if (rX != 0) {
1964       sb.append("; rotate x");
1965       truncate2(sb, rX);
1966     }
1967     if (rY != 0) {
1968       sb.append("; rotate y");
1969       truncate2(sb, rY);
1970     }
1971     if (rZ != 0) {
1972       sb.append("; rotate z");
1973       truncate2(sb, rZ);
1974     }
1975     sb.append(";");
1976     addZoomTranslationNavigationText(sb);
1977     return sb.toString();
1978   }
1979 
addZoomTranslationNavigationText(SB sb)1980   private void addZoomTranslationNavigationText(SB sb) {
1981     if (zmPct != 100) {
1982       sb.append(" zoom");
1983       truncate2(sb, zmPct);
1984       sb.append(";");
1985     }
1986     float tX = getTranslationXPercent();
1987     if (tX != 0) {
1988       sb.append(" translate x");
1989       truncate2(sb, tX);
1990       sb.append(";");
1991     }
1992     float tY = getTranslationYPercent();
1993     if (tY != 0) {
1994       sb.append(" translate y");
1995       truncate2(sb, tY);
1996       sb.append(";");
1997     }
1998     if (modelRadius != rotationRadiusDefault || modelRadius == 10) {
1999       // after ZAP;load APPEND   we need modelRadius, which is 10
2000       sb.append(" set rotationRadius");
2001       truncate2(sb, modelRadius);
2002       sb.append(";");
2003     }
2004     if (mode == MODE_NAVIGATION) {
2005       sb.append("navigate 0 center ").append(Escape.eP(navigationCenter));
2006       sb.append(";navigate 0 translate");
2007       truncate2(sb, getNavigationOffsetPercent('X'));
2008       truncate2(sb, getNavigationOffsetPercent('Y'));
2009       sb.append(";navigate 0 depth ");
2010       truncate2(sb, navigationDepthPercent);
2011       sb.append(";");
2012     }
2013   }
2014 
getRotateZyzText(boolean iAddComment)2015   private String getRotateZyzText(boolean iAddComment) {
2016     SB sb = new SB();
2017     M3 m = (M3) vwr.ms.getInfoM("defaultOrientationMatrix");
2018     if (m == null) {
2019       m = matrixRotate;
2020     } else {
2021       m = M3.newM3(m);
2022       m.invert();
2023       m.mul2(matrixRotate, m);
2024     }
2025     float m22 = m.m22;
2026     float rY = (float) (Math.acos(m22) * degreesPerRadian);
2027     float rZ1, rZ2;
2028     if (m22 > .999f || m22 < -.999f) {
2029       rZ1 = (float) (Math.atan2(m.m10, m.m11) * degreesPerRadian);
2030       rZ2 = 0;
2031     } else {
2032       rZ1 = (float) (Math.atan2(m.m21, -m.m20) * degreesPerRadian);
2033       rZ2 = (float) (Math.atan2(m.m12, m.m02) * degreesPerRadian);
2034     }
2035     if (rZ1 != 0 && rY != 0 && rZ2 != 0 && iAddComment)
2036       sb.append("#Follows Z-Y-Z convention for Euler angles\n");
2037     sb.append("reset");
2038     sb.append(";center ").append(getCenterText());
2039     if (rZ1 != 0) {
2040       sb.append("; rotate z");
2041       truncate2(sb, rZ1);
2042     }
2043     if (rY != 0) {
2044       sb.append("; rotate y");
2045       truncate2(sb, rY);
2046     }
2047     if (rZ2 != 0) {
2048       sb.append("; rotate z");
2049       truncate2(sb, rZ2);
2050     }
2051     sb.append(";");
2052     addZoomTranslationNavigationText(sb);
2053     return sb.toString();
2054   }
2055 
truncate0(SB sb, float val)2056   static private void truncate0(SB sb, float val) {
2057     sb.appendC(' ');
2058     sb.appendI(Math.round(val));
2059   }
2060 
truncate2(SB sb, float val)2061   static private void truncate2(SB sb, float val) {
2062     sb.appendC(' ');
2063     sb.appendF(Math.round(val * 100) / 100f);
2064   }
2065 
2066   /* ***************************************************************
2067    * Spin support
2068    ****************************************************************/
2069 
setSpinXYZ(float x, float y, float z)2070   void setSpinXYZ(float x, float y, float z) {
2071     if (!Float.isNaN(x))
2072       spinX = x;
2073     if (!Float.isNaN(y))
2074       spinY = y;
2075     if (!Float.isNaN(z))
2076       spinZ = z;
2077     if (isSpinInternal || isSpinFixed)
2078       clearSpin();
2079   }
2080 
setSpinFps(int value)2081   void setSpinFps(int value) {
2082     if (value <= 0)
2083       value = 1;
2084     else if (value > 50)
2085       value = 50;
2086     spinFps = value;
2087   }
2088 
setNavXYZ(float x, float y, float z)2089   public void setNavXYZ(float x, float y, float z) {
2090     if (!Float.isNaN(x))
2091       navX = x;
2092     if (!Float.isNaN(y))
2093       navY = y;
2094     if (!Float.isNaN(z))
2095       navZ = z;
2096   }
2097 
clearSpin()2098   private void clearSpin() {
2099     setSpinOff();
2100     setNavOn(false);
2101     isSpinInternal = false;
2102     isSpinFixed = false;
2103     //back to the Chime defaults
2104   }
2105 
2106   public boolean spinOn;
2107 
2108   public boolean navOn;
2109 
2110   private boolean spinIsGesture;
2111 
setSpinOn()2112   public void setSpinOn() {
2113     setSpin(null, true, Float.MAX_VALUE, null, null, null, false);
2114   }
2115 
setSpinOff()2116   public void setSpinOff() {
2117     setSpin(null, false, Float.MAX_VALUE, null, null, null, false);
2118   }
2119 
setSpin(JmolScriptEvaluator eval, boolean spinOn, float endDegrees, Lst<P3> endPositions, float[] dihedralList, BS bsAtoms, boolean isGesture)2120   private void setSpin(JmolScriptEvaluator eval, boolean spinOn,
2121                        float endDegrees, Lst<P3> endPositions,
2122                        float[] dihedralList, BS bsAtoms, boolean isGesture) {
2123     if (navOn && spinOn)
2124       setNavOn(false);
2125     if (this.spinOn == spinOn)
2126       return;
2127     this.spinOn = spinOn;
2128     vwr.g.setB("_spinning", spinOn);
2129     if (spinOn) {
2130       if (spinThread == null) {
2131         spinThread = (JmolThread) Interface.getOption("thread.SpinThread", vwr,
2132             "tm");
2133         spinThread.setManager(this, vwr,
2134             new Object[] { Float.valueOf(endDegrees), endPositions,
2135                 dihedralList, bsAtoms, isGesture ? Boolean.TRUE : null });
2136         spinIsGesture = isGesture;
2137         if ((Float.isNaN(endDegrees) || endDegrees == Float.MAX_VALUE || !vwr.g.waitForMoveTo)) {
2138           spinThread.start();
2139         } else {
2140           spinThread.setEval(eval);
2141           spinThread.run();
2142         }
2143       }
2144     } else if (spinThread != null) {
2145       spinThread.reset();
2146       spinThread = null;
2147     }
2148   }
2149 
setNavOn(boolean navOn)2150   public void setNavOn(boolean navOn) {
2151     if (Float.isNaN(navFps))
2152       return;
2153     boolean wasOn = this.navOn;
2154     if (navOn && spinOn)
2155       setSpin(null, false, 0, null, null, null, false);
2156     this.navOn = navOn;
2157     vwr.g.setB("_navigating", navOn);
2158     if (!navOn)
2159       navInterrupt();
2160     if (navOn) {
2161       if (navX == 0 && navY == 0 && navZ == 0)
2162         navZ = 1;
2163       if (navFps == 0)
2164         navFps = 10;
2165       if (spinThread == null) {
2166         spinThread = (JmolThread) Interface.getOption("thread.SpinThread", vwr,
2167             "tm");
2168         spinThread.setManager(this, vwr, null);
2169         spinThread.start();
2170       }
2171     } else if (wasOn) {
2172       if (spinThread != null) {
2173         spinThread.interrupt();
2174         spinThread = null;
2175       }
2176     }
2177   }
2178 
2179   public boolean vibrationOn;
2180   float vibrationPeriod;
2181   public int vibrationPeriodMs;
2182   private float vibrationScale;
2183   private P3 vibrationT = new P3();
2184 
2185   // only vibrationT.x is used for vibration; modulation options not implemented
2186   // but they could be implemented as a "slice"
2187 
setVibrationScale(float scale)2188   void setVibrationScale(float scale) {
2189     vibrationScale = scale;
2190   }
2191 
2192   /**
2193    * sets the period of vibration -- period > 0: sets the period and turns
2194    * vibration on -- period < 0: sets the period but does not turn vibration on
2195    * -- period = 0: sets the period to zero and turns vibration off -- period
2196    * Float.NaN: uses current setting (frame change)
2197    *
2198    * @param period
2199    */
setVibrationPeriod(float period)2200   public void setVibrationPeriod(float period) {
2201     if (Float.isNaN(period)) {
2202       // NaN -- new frame check
2203       period = vibrationPeriod;
2204     } else if (period == 0) {
2205       vibrationPeriod = 0;
2206       vibrationPeriodMs = 0;
2207     } else {
2208       vibrationPeriod = Math.abs(period);
2209       vibrationPeriodMs = (int) (vibrationPeriod * 1000);
2210       if (period > 0)
2211         return;
2212       period = -period;
2213     }
2214     setVibrationOn(period > 0
2215         && (vwr.ms.getLastVibrationVector(vwr.am.cmi, 0) >= 0));
2216   }
2217 
setVibrationT(float t)2218   public void setVibrationT(float t) {
2219     vibrationT.x = t;
2220     if (vibrationScale == 0)
2221       vibrationScale = vwr.g.vibrationScale;
2222   }
2223 
isVibrationOn()2224   boolean isVibrationOn() {
2225     return vibrationOn;
2226   }
2227 
setVibrationOn(boolean vibrationOn)2228   private void setVibrationOn(boolean vibrationOn) {
2229     if (!vibrationOn) {
2230       if (vibrationThread != null) {
2231         vibrationThread.interrupt();
2232         vibrationThread = null;
2233       }
2234       this.vibrationOn = false;
2235       vibrationT.x = 0;
2236       return;
2237     }
2238     if (vwr.ms.mc < 1) {
2239       this.vibrationOn = false;
2240       vibrationT.x = 0;
2241       return;
2242     }
2243     if (vibrationThread == null) {
2244       vibrationThread = (JmolThread) Interface.getOption(
2245           "thread.VibrationThread", vwr, "tm");
2246       vibrationThread.setManager(this, vwr, null);
2247       vibrationThread.start();
2248     }
2249     this.vibrationOn = true;
2250   }
2251 
clearVibration()2252   private void clearVibration() {
2253     setVibrationOn(false);
2254     vibrationScale = 0;
2255   }
2256 
2257   STER stereoMode = STER.NONE;
2258   int[] stereoColors;
2259   boolean stereoDoubleDTI, stereoDoubleFull;
2260 
setStereoMode2(int[] twoColors)2261   void setStereoMode2(int[] twoColors) {
2262     stereoMode = STER.CUSTOM;
2263     stereoColors = twoColors;
2264   }
2265 
setStereoMode(STER stereoMode)2266   void setStereoMode(STER stereoMode) {
2267     stereoColors = null;
2268     this.stereoMode = stereoMode;
2269     stereoDoubleDTI = (stereoMode == STER.DTI);
2270     stereoDoubleFull = (stereoMode == STER.DOUBLE);
2271   }
2272 
2273   float stereoDegrees = Float.NaN; // set in state manager
2274   float stereoRadians;
2275 
setStereoDegrees(float stereoDegrees)2276   void setStereoDegrees(float stereoDegrees) {
2277     this.stereoDegrees = stereoDegrees;
2278     stereoRadians = stereoDegrees * JC.radiansPerDegree;
2279   }
2280 
2281   boolean stereoFrame;
2282 
2283   protected final M3 matrixStereo = new M3();
2284 
getStereoRotationMatrix(boolean stereoFrame)2285   synchronized M3 getStereoRotationMatrix(boolean stereoFrame) {
2286     this.stereoFrame = stereoFrame;
2287     if (!stereoFrame)
2288       return matrixRotate;
2289     matrixTemp3.setAsYRotation(-stereoRadians);
2290     matrixStereo.mul2(matrixTemp3, matrixRotate);
2291     return matrixStereo;
2292   }
2293 
2294   /////////// rotation center ////////////
2295 
2296   //from Frame:
2297 
2298   public boolean windowCentered;
2299 
isWindowCentered()2300   public boolean isWindowCentered() {
2301     return windowCentered;
2302   }
2303 
setWindowCentered(boolean TF)2304   void setWindowCentered(boolean TF) {
2305     windowCentered = TF;
2306     resetNavigationPoint(true);
2307   }
2308 
setRotationRadius(float angstroms, boolean doAll)2309   public float setRotationRadius(float angstroms, boolean doAll) {
2310     angstroms = (modelRadius = (angstroms <= 0 ? vwr.ms.calcRotationRadius(
2311         vwr.am.cmi, fixedRotationCenter, true) : angstroms));
2312     if (doAll)
2313       vwr.setRotationRadius(angstroms, false);
2314     return angstroms;
2315   }
2316 
setRotationCenterAndRadiusXYZ(T3 newCenterOfRotation, boolean andRadius)2317   private void setRotationCenterAndRadiusXYZ(T3 newCenterOfRotation,
2318                                              boolean andRadius) {
2319     resetNavigationPoint(false);
2320     if (newCenterOfRotation == null) {
2321       setFixedRotationCenter(rotationCenterDefault);
2322       modelRadius = rotationRadiusDefault;
2323       return;
2324     }
2325     setFixedRotationCenter(newCenterOfRotation);
2326     if (andRadius && windowCentered)
2327       modelRadius = vwr.ms.calcRotationRadius(vwr.am.cmi, fixedRotationCenter, true);
2328   }
2329 
setNewRotationCenter(P3 center, boolean doScale)2330   void setNewRotationCenter(P3 center, boolean doScale) {
2331     // once we have the center, we need to optionally move it to
2332     // the proper XY position and possibly scale
2333     if (center == null)
2334       center = rotationCenterDefault;
2335     if (windowCentered) {
2336       translateToPercent('x', 0);
2337       translateToPercent('y', 0);///CenterTo(0, 0);
2338       setRotationCenterAndRadiusXYZ(center, true);
2339       if (doScale)
2340         resetFitToScreen(true);
2341     } else {
2342       moveRotationCenter(center, true);
2343     }
2344   }
2345 
2346   // from Viewer:
2347 
moveRotationCenter(P3 center, boolean toXY)2348   public void moveRotationCenter(P3 center, boolean toXY) {
2349     setRotationCenterAndRadiusXYZ(center, false);
2350     if (toXY)
2351       setRotationPointXY(fixedRotationCenter);
2352   }
2353 
setCenter()2354   void setCenter() {
2355     setRotationCenterAndRadiusXYZ(fixedRotationCenter, true);
2356   }
2357 
setCenterAt(int relativeTo, P3 pt)2358   public void setCenterAt(int relativeTo, P3 pt) {
2359     P3 pt1 = P3.newP(pt);
2360     switch (relativeTo) {
2361     case T.absolute:
2362       break;
2363     case T.average:
2364       pt1.add(vwr.ms.getAverageAtomPoint());
2365       break;
2366     case T.boundbox:
2367       pt1.add(vwr.getBoundBoxCenter());
2368       break;
2369     default:
2370       pt1.setT(rotationCenterDefault);
2371       break;
2372     }
2373     setRotationCenterAndRadiusXYZ(pt1, true);
2374     resetFitToScreen(true);
2375   }
2376 
2377   /* ***************************************************************
2378    * Navigation support
2379    ****************************************************************/
2380 
2381   final P3 frameOffset = new P3();
2382   P3[] frameOffsets;
2383   public BS bsFrameOffsets;
2384 
setFrameOffset(int modelIndex)2385   void setFrameOffset(int modelIndex) {
2386     if (frameOffsets == null || modelIndex < 0
2387         || modelIndex >= frameOffsets.length)
2388       frameOffset.set(0, 0, 0);
2389     else
2390       frameOffset.setT(frameOffsets[modelIndex]);
2391   }
2392 
2393   /////////// Allow during-rendering mouse operations ///////////
2394 
2395   BS bsSelectedAtoms;
2396   P3 ptOffset = new P3();
2397 
setSelectedTranslation(BS bsAtoms, char xyz, int xy)2398   void setSelectedTranslation(BS bsAtoms, char xyz, int xy) {
2399     this.bsSelectedAtoms = bsAtoms;
2400     switch (xyz) {
2401     case 'X':
2402     case 'x':
2403       ptOffset.x += xy;
2404       break;
2405     case 'Y':
2406     case 'y':
2407       ptOffset.y += xy;
2408       break;
2409     case 'Z':
2410     case 'z':
2411       ptOffset.z += xy;
2412       break;
2413     }
2414   }
2415 
2416   /////////////////////////// old TransfomManager11 ////////////////////
2417 
2418   final public static int NAV_MODE_IGNORE = -2;
2419   final public static int NAV_MODE_ZOOMED = -1;
2420   final public static int NAV_MODE_NONE = 0;
2421   final public static int NAV_MODE_RESET = 1;
2422   final public static int NAV_MODE_NEWXY = 2;
2423   final public static int NAV_MODE_NEWXYZ = 3;
2424   final public static int NAV_MODE_NEWZ = 4;
2425 
2426   public int navMode = NAV_MODE_RESET;
2427   public float zoomFactor = Float.MAX_VALUE;
2428 
2429   public float navigationSlabOffset;
2430 
setNavFps(int navFps)2431   protected void setNavFps(int navFps) {
2432     this.navFps = navFps;
2433   }
2434 
2435   /**
2436    * sets all camera and scale factors needed by the specific perspective model
2437    * instantiated
2438    *
2439    */
calcCameraFactors()2440   public void calcCameraFactors() {
2441     // (m) model coordinates
2442     // (s) screen coordinates = (m) * screenPixelsPerAngstrom
2443     // (p) plane coordinates = (s) / screenPixelCount
2444 
2445     if (Float.isNaN(cameraDepth)) {
2446       cameraDepth = cameraDepthSetting;
2447       zoomFactor = Float.MAX_VALUE;
2448     }
2449 
2450     // reference point where p=0
2451     cameraDistance = cameraDepth * screenPixelCount; // (s)
2452 
2453     // distance from camera to midPlane of model (p=0.5)
2454     // the factor to apply based on screen Z
2455     referencePlaneOffset = cameraDistance + screenPixelCount / 2f; // (s)
2456 
2457     // conversion factor Angstroms --> pixels
2458     // so that "full window" is visualRange
2459     scalePixelsPerAngstrom = (scale3D && !perspectiveDepth
2460         && mode != MODE_NAVIGATION ? 72 / scale3DAngstromsPerInch
2461         * (antialias ? 2 : 1) : screenPixelCount / visualRangeAngstroms); // (s/m)
2462 
2463     if (mode != MODE_NAVIGATION)
2464       mode = (camera.z == 0 ? MODE_STANDARD : MODE_PERSPECTIVE_PYMOL);
2465     // still not 100% certain why we have to do this, but H115W.PinM.PSE requires it
2466     perspectiveShiftXY.set(camera.z == 0 ? 0 : camera.x
2467         * scalePixelsPerAngstrom / screenWidth * 100, camera.z == 0 ? 0
2468         : camera.y * scalePixelsPerAngstrom / screenHeight * 100, 0);
2469 
2470     // model radius in pixels
2471     modelRadiusPixels = modelRadius * scalePixelsPerAngstrom; // (s)
2472 
2473 
2474     // model center offset for zoom 100
2475     float offset100 = (2 * modelRadius) / visualRangeAngstroms * referencePlaneOffset; // (s)
2476 
2477     //    System.out.println("sppA " + scalePixelsPerAngstrom + " pD " +
2478     //     perspectiveDepth + " s3dspi " + scale3DAngstromsPerInch + " "
2479     //     + " spC " + screenPixelCount + " vR " + visualRange
2480     //     + " sDPPA " + scaleDefaultPixelsPerAngstrom);
2481 
2482     if (mode == MODE_NAVIGATION) {
2483       calcNavCameraFactors(offset100);
2484       return;
2485     }
2486     // nonNavigation mode -- to match Jmol 10.2 at midplane (caffeine.xyz)
2487     // flag that we have left navigation mode
2488     zoomFactor = Float.MAX_VALUE;
2489     // we place the model at the referencePlaneOffset offset and then change
2490     // the scale
2491     modelCenterOffset = referencePlaneOffset;
2492     // now factor the scale by distance from camera and zoom
2493     if (!scale3D || perspectiveDepth)
2494       scalePixelsPerAngstrom *= (modelCenterOffset / offset100) * zmPct / 100; // (s/m)
2495 
2496 
2497     // so that's sppa = (spc / vR) * rPO * (vR / 2) / mR * rPO = spc/2/mR
2498 
2499     modelRadiusPixels = modelRadius * scalePixelsPerAngstrom; // (s)
2500 
2501     //    System.out.println("transformman zoom scalppa modelrad " + zoomPercent + " " +
2502     //     scalePixelsPerAngstrom + " " + modelRadiusPixels + " " + visualRange
2503     //     + " -- "+ vwr.dimScreen.width+ "  "+ vwr.dimScreen.height);
2504     //    System.out.println("modelCenterOffset " + modelCenterOffset + " " + modelRadius);
2505   }
2506 
calcNavCameraFactors(float offset100)2507   private void calcNavCameraFactors(float offset100) {
2508     if (zoomFactor == Float.MAX_VALUE) {
2509       // entry point
2510       if (zmPct > MAXIMUM_ZOOM_PERSPECTIVE_DEPTH)
2511         zmPct = MAXIMUM_ZOOM_PERSPECTIVE_DEPTH;
2512       // screen offset to fixed rotation center
2513       modelCenterOffset = offset100 * 100 / zmPct;
2514     } else if (prevZoomSetting != zmPctSet) {
2515       if (zoomRatio == 0) // scripted change zoom xxx
2516         modelCenterOffset = offset100 * 100 / zmPctSet;
2517       else
2518         // fractional change by script or mouse
2519         modelCenterOffset += (1 - zoomRatio) * referencePlaneOffset;
2520       navMode = NAV_MODE_ZOOMED;
2521     }
2522     prevZoomSetting = zmPctSet;
2523     zoomFactor = modelCenterOffset / referencePlaneOffset;
2524     // infinite or negative value means there is no corresponding non-navigating
2525     // zoom setting
2526     zmPct = (zoomFactor == 0 ? MAXIMUM_ZOOM_PERSPECTIVE_DEPTH : offset100
2527         / modelCenterOffset * 100);
2528 
2529   }
2530 
2531   /**
2532    * calculate the perspective factor based on z
2533    *
2534    * @param z
2535    * @return perspectiveFactor
2536    */
getPerspectiveFactor(float z)2537   public float getPerspectiveFactor(float z) {
2538     return (z <= 0 ? referencePlaneOffset : referencePlaneOffset / z);
2539   }
2540 
unTransformPoint(T3 screenPt, T3 coordPt)2541   public void unTransformPoint(T3 screenPt, T3 coordPt) {
2542     // mostly for exporters and navigation mode
2543     // but also for translate selected, assign atom,
2544     //
2545     untransformedPoint.setT(screenPt);
2546     switch (mode) {
2547     case MODE_NAVIGATION:
2548       untransformedPoint.x -= navigationOffset.x;
2549       untransformedPoint.y -= navigationOffset.y;
2550       break;
2551     case MODE_PERSPECTIVE_PYMOL:
2552       fScrPt.x += perspectiveShiftXY.x;
2553       fScrPt.y += perspectiveShiftXY.y;
2554       //$FALL-THROUGH$
2555     case MODE_STANDARD:
2556       untransformedPoint.x -= fixedRotationOffset.x;
2557       untransformedPoint.y -= fixedRotationOffset.y;
2558     }
2559     if (perspectiveDepth) {
2560       float factor = getPerspectiveFactor(untransformedPoint.z);
2561       untransformedPoint.x /= factor;
2562       untransformedPoint.y /= factor;
2563     }
2564     switch (mode) {
2565     case MODE_NAVIGATION:
2566       untransformedPoint.x += navigationShiftXY.x;
2567       untransformedPoint.y += navigationShiftXY.y;
2568       break;
2569     case MODE_PERSPECTIVE_PYMOL:
2570       untransformedPoint.x -= perspectiveShiftXY.x;
2571       untransformedPoint.y -= perspectiveShiftXY.y;
2572       break;
2573     }
2574     matrixTransformInv.rotTrans2(untransformedPoint, coordPt);
2575   }
2576 
2577 //  boolean canNavigate() {
2578 //    return true;
2579 //  }
2580 
2581   /**
2582    * something has arisen that requires resetting of the navigation point.
2583    *
2584    * @param doResetSlab
2585    */
resetNavigationPoint(boolean doResetSlab)2586   protected void resetNavigationPoint(boolean doResetSlab) {
2587     if (zmPct < 5 && mode != MODE_NAVIGATION) {
2588       perspectiveDepth = true;
2589       mode = MODE_NAVIGATION;
2590       return;
2591     }
2592     if (mode == MODE_NAVIGATION) {
2593       navMode = NAV_MODE_RESET;
2594       slabPercentSetting = 0;
2595       perspectiveDepth = true;
2596     } else if (doResetSlab) {
2597       slabPercentSetting = 100;
2598     }
2599     vwr.setFloatProperty("slabRange", 0);
2600     if (doResetSlab) {
2601       setSlabEnabled(mode == MODE_NAVIGATION);
2602     }
2603     zoomFactor = Float.MAX_VALUE;
2604     zmPctSet = zmPct;
2605   }
2606 
2607   /**
2608    * scripted entry point for navigation
2609    *
2610    * @param pt
2611    */
setNavigatePt(P3 pt)2612   public void setNavigatePt(P3 pt) {
2613     // from MoveToThread
2614     navigationCenter.setT(pt);
2615     navMode = NAV_MODE_NEWXYZ;
2616     navigating = true;
2617     finalizeTransformParameters();
2618     navigating = false;
2619   }
2620 
setNavigationSlabOffsetPercent(float percent)2621   void setNavigationSlabOffsetPercent(float percent) {
2622     vwr.g.setF("navigationSlab", percent);
2623     calcCameraFactors(); // current
2624     navigationSlabOffset = percent / 50 * modelRadiusPixels;
2625   }
2626 
getNavigationOffset()2627   public P3 getNavigationOffset() {
2628     transformPt3f(navigationCenter, navigationOffset);
2629     return navigationOffset;
2630   }
2631 
getNavPtHeight()2632   public float getNavPtHeight() {
2633     //boolean navigateSurface = vwr.getNavigateSurface();
2634     return height / 2f;//(navigateSurface ? 1f : 2f);
2635   }
2636 
getNavigationOffsetPercent(char XorY)2637   public float getNavigationOffsetPercent(char XorY) {
2638     getNavigationOffset();
2639     if (width == 0 || height == 0)
2640       return 0;
2641     return (XorY == 'X' ? (navigationOffset.x - width / 2f) * 100f / width
2642         : (navigationOffset.y - getNavPtHeight()) * 100f / height);
2643   }
2644 
getNavigationText(boolean addComments)2645   protected String getNavigationText(boolean addComments) {
2646     String s = (addComments ? " /* navigation center, translation, depth */ "
2647         : " ");
2648     if (mode != MODE_NAVIGATION)
2649       return s + "{0 0 0} 0 0 0";
2650     getNavigationOffset();
2651     return s + Escape.eP(navigationCenter) + " "
2652         + getNavigationOffsetPercent('X') + " "
2653         + getNavigationOffsetPercent('Y') + " " + navigationDepthPercent;
2654   }
2655 
setScreenParameters(int screenWidth, int screenHeight, boolean useZoomLarge, boolean antialias, boolean resetSlab, boolean resetZoom)2656   void setScreenParameters(int screenWidth, int screenHeight,
2657                            boolean useZoomLarge, boolean antialias,
2658                            boolean resetSlab, boolean resetZoom) {
2659     P3 pt = (mode == MODE_NAVIGATION ? P3.newP(navigationCenter) : null);
2660     P3 ptoff = P3.newP(navigationOffset);
2661     ptoff.x = ptoff.x / width;
2662     ptoff.y = ptoff.y / height;
2663     setScreenParameters0(screenWidth, screenHeight, useZoomLarge, antialias,
2664         resetSlab, resetZoom);
2665     if (pt != null) {
2666       navigationCenter.setT(pt);
2667       navTranslatePercentOrTo(-1, ptoff.x * width, ptoff.y * height);
2668       setNavigatePt(pt);
2669     }
2670   }
2671 
2672   //////////////  optional navigation support ///////////////////////
2673 
2674   private JmolNavigatorInterface nav;
2675 
navInterrupt()2676   private void navInterrupt() {
2677     if (nav != null)
2678       nav.interrupt();
2679   }
2680 
getNav()2681   private boolean getNav() {
2682     if (nav != null)
2683       return true;
2684     nav = (JmolNavigatorInterface) Interface.getOption("navigate.Navigator",
2685         vwr, "tm");
2686     if (nav == null)
2687       return false;
2688     nav.set(this, vwr);
2689     return true;
2690   }
2691 
navigateList(JmolScriptEvaluator eval, Lst<Object[]> list)2692   public void navigateList(JmolScriptEvaluator eval, Lst<Object[]> list) {
2693     if (getNav())
2694       nav.navigateList(eval, list);
2695   }
2696 
2697   /**
2698    * scripted entry point for navigation
2699    *
2700    * @param rotAxis
2701    * @param degrees
2702    */
navigateAxis(V3 rotAxis, float degrees)2703   public void navigateAxis(V3 rotAxis, float degrees) {
2704     if (getNav())
2705       nav.navigateAxis(rotAxis, degrees);
2706   }
2707 
setNavigationOffsetRelative()2708   public void setNavigationOffsetRelative() {//boolean navigatingSurface) {
2709     if (getNav())
2710       nav.setNavigationOffsetRelative();//navigatingSurface);
2711   }
2712 
2713   /**
2714    * entry point for keyboard-based navigation
2715    *
2716    * @param keyCode
2717    *        0 indicates key released
2718    * @param modifiers
2719    *        shift,alt,ctrl
2720    */
navigateKey(int keyCode, int modifiers)2721   synchronized void navigateKey(int keyCode, int modifiers) {
2722     if (getNav())
2723       nav.navigateKey(keyCode, modifiers);
2724   }
2725 
2726   /**
2727    * sets the position of the navigation offset relative to the model (50%
2728    * center; 0% rear, 100% front; can be <0 or >100)
2729    *
2730    * @param percent
2731    */
setNavigationDepthPercent(float percent)2732   public void setNavigationDepthPercent(float percent) {
2733     if (getNav())
2734       nav.setNavigationDepthPercent(percent);
2735   }
2736 
2737   /**
2738    * seconds < 0 means "to (x,y)"; >= 0 mean "to (x%, y%)"
2739    *
2740    * @param seconds
2741    * @param x
2742    * @param y
2743    */
navTranslatePercentOrTo(float seconds, float x, float y)2744   public void navTranslatePercentOrTo(float seconds, float x, float y) {
2745     if (getNav())
2746       nav.navTranslatePercentOrTo(seconds, x, y);
2747   }
2748 
2749   /**
2750    * All the magic happens here. all navigation effects go through this method
2751    *
2752    */
calcNavigationPoint()2753   protected void calcNavigationPoint() {
2754     if (getNav())
2755       nav.calcNavigationPoint();
2756   }
2757 
2758   /**
2759    *
2760    * @return the script that defines the current navigation state
2761    *
2762    */
getNavigationState()2763   protected String getNavigationState() {
2764     return (mode == MODE_NAVIGATION && getNav() ? nav.getNavigationState() : "");
2765   }
2766 
2767 }
2768