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