001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021package org.biojava.nbio.structure.symmetry.axis; 022 023import org.biojava.nbio.structure.geometry.CalcPoint; 024import org.biojava.nbio.structure.geometry.MomentsOfInertia; 025import org.biojava.nbio.structure.symmetry.core.Helix; 026import org.biojava.nbio.structure.symmetry.core.HelixLayers; 027import org.biojava.nbio.structure.symmetry.core.QuatSymmetryResults; 028import org.biojava.nbio.structure.symmetry.core.QuatSymmetrySubunits; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import javax.vecmath.*; 033 034import java.util.*; 035 036public class HelixAxisAligner extends AxisAligner { 037 038 private static final Logger logger = LoggerFactory 039 .getLogger(HelixAxisAligner.class); 040 041 042 private static final Vector3d Y_AXIS = new Vector3d(0,1,0); 043 private static final Vector3d Z_AXIS = new Vector3d(0,0,1); 044 045 private QuatSymmetrySubunits subunits = null; 046 private HelixLayers helixLayers = null; 047 048 private Matrix4d transformationMatrix = new Matrix4d(); 049 private Matrix4d reverseTransformationMatrix = new Matrix4d(); 050 private Vector3d referenceVector = new Vector3d(); 051 private Vector3d principalRotationVector = new Vector3d(); 052 private Vector3d[] principalAxesOfInertia = null; 053 private List<List<Integer>> alignedOrbits = null; 054 055 private Vector3d minBoundary = new Vector3d(); 056 private Vector3d maxBoundary = new Vector3d(); 057 private double xzRadiusMax = Double.MIN_VALUE; 058 059 boolean modified = true; 060 061 public HelixAxisAligner(QuatSymmetryResults results) { 062 this.subunits = new QuatSymmetrySubunits(results.getSubunitClusters()); 063 this.helixLayers = results.getHelixLayers(); 064 if (subunits == null) { 065 throw new IllegalArgumentException("HelixAxisTransformation: Subunits are null"); 066 } else if (helixLayers == null) { 067 throw new IllegalArgumentException("HelixAxisTransformation: HelixLayers is null"); 068 } else if (subunits.getSubunitCount() == 0) { 069 throw new IllegalArgumentException("HelixAxisTransformation: Subunits is empty"); 070 } else if (helixLayers.size() == 0) { 071 throw new IllegalArgumentException("HelixAxisTransformation: HelixLayers is empty"); 072 } 073 } 074 075 076 /* (non-Javadoc) 077 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getTransformation() 078 */ 079 @Override 080 public String getSymmetry() { 081 run(); 082 return "H"; 083 } 084 085 /* (non-Javadoc) 086 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getTransformation() 087 */ 088 @Override 089 public Matrix4d getTransformation() { 090 run(); 091 return transformationMatrix; 092 } 093 094 /* (non-Javadoc) 095 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getRotationMatrix() 096 */ 097 @Override 098 public Matrix3d getRotationMatrix() { 099 run(); 100 Matrix3d m = new Matrix3d(); 101 transformationMatrix.getRotationScale(m); 102 return m; 103 } 104 105 /* (non-Javadoc) 106 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getReverseTransformation() 107 */ 108 @Override 109 public Matrix4d getReverseTransformation() { 110 run(); 111 return reverseTransformationMatrix; 112 } 113 114 /* (non-Javadoc) 115 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getPrincipalRotationAxis() 116 */ 117 @Override 118 public Vector3d getPrincipalRotationAxis() { 119 run(); 120 return principalRotationVector; 121 } 122 123 /* (non-Javadoc) 124 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getRotationReferenceAxis() 125 */ 126 @Override 127 public Vector3d getRotationReferenceAxis() { 128 run(); 129 return referenceVector; 130 } 131 132 /* (non-Javadoc) 133 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getPrincipalAxesOfInertia() 134 */ 135 @Override 136 public Vector3d[] getPrincipalAxesOfInertia() { 137 run(); 138 return principalAxesOfInertia; 139 } 140 141 /* (non-Javadoc) 142 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getDimension() 143 */ 144 @Override 145 public Vector3d getDimension() { 146 run(); 147 Vector3d dimension = new Vector3d(); 148 dimension.sub(maxBoundary, minBoundary); 149 dimension.scale(0.5); 150 return dimension; 151 } 152 153 /* (non-Javadoc) 154 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getXYRadius() 155 */ 156 @Override 157 public double getRadius() { 158 run(); 159 return xzRadiusMax; 160 } 161 162 /* (non-Javadoc) 163 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getGeometicCenterTransformation() 164 */ 165 @Override 166 public Matrix4d getGeometicCenterTransformation() { 167 run(); 168 169 Matrix4d geometricCentered = new Matrix4d(reverseTransformationMatrix); 170 geometricCentered.setTranslation(new Vector3d(getGeometricCenter())); 171 172 return geometricCentered; 173 } 174 175 /* (non-Javadoc) 176 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getGeometricCenter() 177 */ 178 @Override 179 public Point3d getGeometricCenter() { 180 run(); 181 182 Point3d geometricCenter = new Point3d(); 183 Vector3d translation = new Vector3d(); 184// reverseTransformationMatrix.get(translation); 185 186 // TODO does this apply to the helic case? 187 // calculate adjustment around z-axis and transform adjustment to 188 // original coordinate frame with the reverse transformation 189 190// Vector3d corr = new Vector3d(0,minBoundary.y+getDimension().y, 0); 191// reverseTransformationMatrix.transform(corr); 192// geometricCenter.set(corr); 193 194 reverseTransformationMatrix.transform(translation); 195 geometricCenter.add(translation); 196 return geometricCenter; 197 } 198 199 /* (non-Javadoc) 200 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getCentroid() 201 */ 202 @Override 203 public Point3d getCentroid() { 204 return new Point3d(subunits.getCentroid()); 205 } 206 207 /* (non-Javadoc) 208 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getSubunits() 209 */ 210 @Override 211 public QuatSymmetrySubunits getSubunits() { 212 return subunits; 213 } 214 215 public HelixLayers getHelixLayers() { 216 run(); 217 return helixLayers; 218 } 219 220 /* (non-Javadoc) 221 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getOrbits() 222 */ 223 @Override 224 public List<List<Integer>> getOrbits() { 225 run(); 226 return alignedOrbits; 227 } 228 229 /** 230 * @return 231 */ 232 233 private void run () { 234 if (modified) { 235 calcPrincipalRotationVector(); 236 calcPrincipalAxes(); 237 calcReferenceVector(); 238 calcTransformation(); 239 // orient helix along Y axis by rotating 90 degrees around X-axis 240 transformationMatrix = reorientHelix(0); 241 242 calcReverseTransformation(); 243 calcBoundaries(); 244 calcAlignedOrbits(); 245 calcCenterOfRotation(); 246 247 // orient helix along Y axis by rotating 90 degrees around X-axis 248// transformationMatrix = reorientHelix(0); 249// calcReverseTransformation(); 250 251 modified = false; 252 } 253 } 254 255 public Point3d calcCenterOfRotation() { 256 List<Integer> line = getLongestLayerLine(); 257 258 // can't determine center of rotation if there are only 2 points 259 // TODO does this ever happen?? 260 if (line.size() < 3) { 261 return subunits.getCentroid(); 262 } 263 264 Point3d centerOfRotation = new Point3d(); 265 List<Point3d> centers = subunits.getOriginalCenters(); 266 267 // calculate helix mid points for each set of 3 adjacent subunits 268 for (int i = 0; i < line.size()-2; i++) { 269 Point3d p1 = new Point3d(centers.get(line.get(i))); 270 Point3d p2 = new Point3d(centers.get(line.get(i+1))); 271 Point3d p3 = new Point3d(centers.get(line.get(i+2))); 272 transformationMatrix.transform(p1); 273 transformationMatrix.transform(p2); 274 transformationMatrix.transform(p3); 275 centerOfRotation.add(getMidPoint(p1, p2, p3)); 276 } 277 278 // average over all midpoints to find best center of rotation 279 centerOfRotation.scale(1/(line.size()-2)); 280 // since helix is aligned along the y-axis, with an origin at y = 0, place the center of rotation there 281 centerOfRotation.y = 0; 282 // transform center of rotation to the original coordinate frame 283 reverseTransformationMatrix.transform(centerOfRotation); 284// System.out.println("center of rotation: " + centerOfRotation); 285 return centerOfRotation; 286 } 287 288 private List<Integer> getLongestLayerLine() { 289 int len = 0; 290 int index = 0; 291 292 Helix helix = helixLayers.getByLargestContacts(); 293 List<List<Integer>> layerLines = helix.getLayerLines(); 294 for (int i = 0; i < layerLines.size(); i++) { 295 if (layerLines.get(i).size() > len) { 296 len = layerLines.get(i).size(); 297 index = i; 298 } 299 } 300 return layerLines.get(index); 301 } 302 303 /** 304 * Return a midpoint of a helix, calculated from three positions 305 * of three adjacent subunit centers. 306 * @param p1 center of first subunit 307 * @param p2 center of second subunit 308 * @param p3 center of third subunit 309 * @return midpoint of helix 310 */ 311 private Point3d getMidPoint(Point3d p1, Point3d p2, Point3d p3) { 312 Vector3d v1 = new Vector3d(); 313 v1.sub(p1, p2); 314 Vector3d v2 = new Vector3d(); 315 v2.sub(p3, p2); 316 Vector3d v3 = new Vector3d(); 317 v3.add(v1); 318 v3.add(v2); 319 v3.normalize(); 320 321 // calculat the total distance between to subunits 322 double dTotal = v1.length(); 323 // calculate the rise along the y-axis. The helix axis is aligned with y-axis, 324 // therfore, the rise between subunits is the y-distance 325 double rise = p2.y - p1.y; 326 // use phythagorean theoremm to calculate chord length between two subunit centers 327 double chord = Math.sqrt(dTotal*dTotal - rise*rise); 328// System.out.println("Chord d: " + dTotal + " rise: " + rise + "chord: " + chord); 329 double angle = helixLayers.getByLargestContacts().getAxisAngle().angle; 330 331 // using the axis angle and the chord length, we can calculate the radius of the helix 332 // http://en.wikipedia.org/wiki/Chord_%28geometry%29 333 double radius = chord/Math.sin(angle/2)/2; // can this go to zero? 334// System.out.println("Radius: " + radius); 335 336 // project the radius onto the vector that points toward the helix axis 337 v3.scale(radius); 338 v3.add(p2); 339// System.out.println("Angle: " + Math.toDegrees(helixLayers.getByLowestAngle().getAxisAngle().angle)); 340 Point3d cor = new Point3d(v3); 341 return cor; 342 } 343 344 private Matrix4d reorientHelix(int index) { 345 Matrix4d matrix = new Matrix4d(); 346 matrix.setIdentity(); 347 matrix.setRotation(new AxisAngle4d(1,0,0,Math.PI/2*(index+1))); 348 matrix.mul(transformationMatrix); 349 return matrix; 350 } 351 352 /** 353 * Returns a list of orbits (an orbit is a cyclic permutation of subunit indices that are related 354 * by a rotation around the principal rotation axis) ordered from the +z direction to the -z direction (z-depth). 355 * Within an orbit, subunit indices are ordered with a clockwise rotation around the z-axis. 356 * @return list of orbits ordered by z-depth 357 */ 358 private void calcAlignedOrbits() { 359 Map<Double, List<Integer>> depthMap = new TreeMap<Double, List<Integer>>(); 360 double[] depth = getSubunitZDepth(); 361 alignedOrbits = calcOrbits(); 362 363 // calculate the mean depth of orbit along z-axis 364 for (List<Integer> orbit: alignedOrbits) { 365 // calculate the mean depth along z-axis for each orbit 366 double meanDepth = 0; 367 for (int subunit: orbit) { 368 meanDepth += depth[subunit]; 369 } 370 meanDepth /= orbit.size(); 371 372 if (depthMap.get(meanDepth) != null) { 373 meanDepth += 0.01; 374 } 375// System.out.println("calcAlignedOrbits: " + meanDepth + " orbit: " + orbit); 376 depthMap.put(meanDepth, orbit); 377 } 378 379 // now fill orbits back into list ordered by depth 380 alignedOrbits.clear(); 381 for (List<Integer> orbit: depthMap.values()) { 382 // order subunit in a clockwise rotation around the z-axis 383 /// starting at the 12 O-clock position (+y position) 384 // TODO how should this be aligned?? 385 // alignWithReferenceAxis(orbit); 386 alignedOrbits.add(orbit); 387 } 388 } 389 390 391 private void calcTransformation() { 392 calcTransformationBySymmetryAxes(); 393 // make sure this value is zero. On Linux this value is 0. 394 transformationMatrix.setElement(3, 3, 1.0); 395 } 396 397 private void calcReverseTransformation() { 398 reverseTransformationMatrix.invert(transformationMatrix); 399 } 400 401 private void calcTransformationBySymmetryAxes() { 402 Vector3d[] axisVectors = new Vector3d[2]; 403 axisVectors[0] = new Vector3d(principalRotationVector); 404 axisVectors[1] = new Vector3d(referenceVector); 405 406 // y,z axis centered at the centroid of the subunits 407 Vector3d[] referenceVectors = new Vector3d[2]; 408 referenceVectors[0] = new Vector3d(Z_AXIS); 409 referenceVectors[1] = new Vector3d(Y_AXIS); 410 411 transformationMatrix = alignAxes(axisVectors, referenceVectors); 412 413 // combine with translation 414 Matrix4d combined = new Matrix4d(); 415 combined.setIdentity(); 416 Vector3d trans = new Vector3d(subunits.getCentroid()); 417 trans.negate(); 418 combined.setTranslation(trans); 419 transformationMatrix.mul(combined); 420 421 // for helical geometry, set a canonical view for the Z direction 422 calcZDirection(); 423 } 424 425 /** 426 * Returns a transformation matrix that rotates refPoints to match 427 * coordPoints 428 * @param refPoints the points to be aligned 429 * @param referenceVectors 430 * @return 431 */ 432 private Matrix4d alignAxes(Vector3d[] axisVectors, Vector3d[] referenceVectors) { 433 Matrix4d m1 = new Matrix4d(); 434 AxisAngle4d a = new AxisAngle4d(); 435 Vector3d axis = new Vector3d(); 436 437 // calculate rotation matrix to rotate refPoints[0] into coordPoints[0] 438 Vector3d v1 = new Vector3d(axisVectors[0]); 439 Vector3d v2 = new Vector3d(referenceVectors[0]); 440 double dot = v1.dot(v2); 441 if (Math.abs(dot) < 0.999) { 442 axis.cross(v1,v2); 443 axis.normalize(); 444 a.set(axis, v1.angle(v2)); 445 m1.set(a); 446 // make sure matrix element m33 is 1.0. It's 0 on Linux. 447 m1.setElement(3, 3, 1.0); 448 } else if (dot > 0) { 449 // parallel axis, nothing to do -> identity matrix 450 m1.setIdentity(); 451 } else if (dot < 0) { 452 // anti-parallel axis, flip around x-axis 453 m1.set(flipX()); 454 } 455 456 // apply transformation matrix to all refPoints 457 m1.transform(axisVectors[0]); 458 m1.transform(axisVectors[1]); 459 460 // calculate rotation matrix to rotate refPoints[1] into coordPoints[1] 461 v1 = new Vector3d(axisVectors[1]); 462 v2 = new Vector3d(referenceVectors[1]); 463 Matrix4d m2 = new Matrix4d(); 464 dot = v1.dot(v2); 465 if (Math.abs(dot) < 0.999) { 466 axis.cross(v1,v2); 467 axis.normalize(); 468 a.set(axis, v1.angle(v2)); 469 m2.set(a); 470 // make sure matrix element m33 is 1.0. It's 0 on Linux. 471 m2.setElement(3, 3, 1.0); 472 } else if (dot > 0) { 473 // parallel axis, nothing to do -> identity matrix 474 m2.setIdentity(); 475 } else if (dot < 0) { 476 // anti-parallel axis, flip around z-axis 477 m2.set(flipZ()); 478 } 479 480 // apply transformation matrix to all refPoints 481 m2.transform(axisVectors[0]); 482 m2.transform(axisVectors[1]); 483 484 // combine the two rotation matrices 485 m2.mul(m1); 486 487 // the RMSD should be close to zero 488 Point3d[] axes = new Point3d[2]; 489 axes[0] = new Point3d(axisVectors[0]); 490 axes[1] = new Point3d(axisVectors[1]); 491 Point3d[] ref = new Point3d[2]; 492 ref[0] = new Point3d(referenceVectors[0]); 493 ref[1] = new Point3d(referenceVectors[1]); 494 if (CalcPoint.rmsd(axes, ref) > 0.1) { 495 logger.warn("AxisTransformation: axes alignment is off. RMSD: " 496 + CalcPoint.rmsd(axes, ref)); 497 } 498 499 return m2; 500 } 501 502 private void calcPrincipalAxes() { 503 MomentsOfInertia moi = new MomentsOfInertia(); 504 505 for (Point3d[] list: subunits.getTraces()) { 506 for (Point3d p: list) { 507 moi.addPoint(p, 1.0); 508 } 509 } 510 principalAxesOfInertia = moi.getPrincipalAxes(); 511 } 512 513 /** 514 * Calculates the min and max boundaries of the structure after it has been 515 * transformed into its canonical orientation. 516 */ 517 private void calcBoundaries() { 518 minBoundary.x = Double.MAX_VALUE; 519 maxBoundary.x = Double.MIN_VALUE; 520 minBoundary.y = Double.MAX_VALUE; 521 maxBoundary.x = Double.MIN_VALUE; 522 minBoundary.z = Double.MAX_VALUE; 523 maxBoundary.z = Double.MIN_VALUE; 524 xzRadiusMax = Double.MIN_VALUE; 525 526 Point3d probe = new Point3d(); 527 528 for (Point3d[] list: subunits.getTraces()) { 529 for (Point3d p: list) { 530 probe.set(p); 531 transformationMatrix.transform(probe); 532 533 minBoundary.x = Math.min(minBoundary.x, probe.x); 534 maxBoundary.x = Math.max(maxBoundary.x, probe.x); 535 minBoundary.y = Math.min(minBoundary.y, probe.y); 536 maxBoundary.y = Math.max(maxBoundary.y, probe.y); 537 minBoundary.z = Math.min(minBoundary.z, probe.z); 538 maxBoundary.z = Math.max(maxBoundary.z, probe.z); 539 xzRadiusMax = Math.max(xzRadiusMax, Math.sqrt(probe.x*probe.x + probe.z * probe.z)); 540 } 541 } 542// System.out.println("MinBoundary: " + minBoundary); 543// System.out.println("MaxBoundary: " + maxBoundary); 544// System.out.println("zxRadius: " + xzRadiusMax); 545 } 546 547 /* 548 * Modifies the rotation part of the transformation axis for 549 * a Cn symmetric complex, so that the narrower end faces the 550 * viewer, and the wider end faces away from the viewer. Example: 3LSV 551 */ 552 private void calcZDirection() { 553 calcBoundaries(); 554 555 // if the longer part of the structure faces towards the back (-z direction), 556 // rotate around y-axis so the longer part faces the viewer (+z direction) 557 if (Math.abs(minBoundary.z) > Math.abs(maxBoundary.z)) { 558 Matrix4d rot = flipY(); 559 rot.mul(transformationMatrix); 560 transformationMatrix.set(rot); 561 } 562 } 563 564 private double[] getSubunitZDepth() { 565 int n = subunits.getSubunitCount(); 566 double[] depth = new double[n]; 567 Point3d probe = new Point3d(); 568 569 // transform subunit centers into z-aligned position and calculate 570 // z-coordinates (depth) along the z-axis. 571 for (int i = 0; i < n; i++) { 572 Point3d p= subunits.getCenters().get(i); 573 probe.set(p); 574 transformationMatrix.transform(probe); 575 depth[i] = probe.z; 576 } 577 return depth; 578 } 579 580 /** 581 * Returns a list of list of subunit ids that form an "orbit", i.e. they 582 * are transformed into each other during a rotation around the principal symmetry axis (z-axis) 583 * @return 584 */ 585 private List<List<Integer>> calcOrbits() { 586 int n = subunits.getSubunitCount(); 587 588 List<List<Integer>> orbits = new ArrayList<List<Integer>>(); 589 for (int i = 0; i < n; i++) { 590 orbits.add(Collections.singletonList(i)); 591 } 592 593 return orbits; 594 } 595 596 /** 597 * Returns a vector along the principal rotation axis for the 598 * alignment of structures along the z-axis 599 * @return principal rotation vector 600 */ 601 private void calcPrincipalRotationVector() { 602// AxisAngle4d axisAngle = helixLayers.getByLowestAngle().getAxisAngle(); 603 AxisAngle4d axisAngle = helixLayers.getByLargestContacts().getAxisAngle(); 604 principalRotationVector = new Vector3d(axisAngle.x, axisAngle.y, axisAngle.z); 605 } 606 607 /** 608 * Returns a vector perpendicular to the principal rotation vector 609 * for the alignment of structures in the xy-plane 610 * @return reference vector 611 */ 612 private void calcReferenceVector() { 613 referenceVector = getReferenceAxisCylic(); 614 615 if (referenceVector == null) { 616 logger.warn("no reference vector found. Using y-axis."); 617 referenceVector = new Vector3d(Y_AXIS); 618 } 619 // make sure reference vector is perpendicular principal roation vector 620 referenceVector = orthogonalize(principalRotationVector, referenceVector); 621 } 622 623 private Vector3d orthogonalize(Vector3d vector1, Vector3d vector2) { 624 double dot = vector1.dot(vector2); 625 Vector3d ref = new Vector3d(vector2); 626// System.out.println("p.r: " + dot); 627// System.out.println("Orig refVector: " + referenceVector); 628 if (dot < 0) { 629 vector2.negate(); 630 } 631 if (Math.abs(dot) < 0.00001) { 632 logger.info("HelixAxisAligner: reference axis parallel"); 633 } 634 vector2.cross(vector1, vector2); 635// System.out.println("Intermed. refVector: " + vector2); 636 vector2.normalize(); 637// referenceVector.cross(referenceVector, principalRotationVector); 638 vector2.cross(vector1, vector2); 639 vector2.normalize(); 640 if (ref.dot(vector2) < 0) { 641 vector2.negate(); 642 } 643// System.out.println("Mod. refVector: " + vector2); 644 return vector2; 645 } 646 /** 647 * Returns the default reference vector for the alignment of Cn structures 648 * @return 649 */ 650 private Vector3d getReferenceAxisCylic() { 651 // get principal axis vector that is perpendicular to the principal 652 // rotation vector 653 Vector3d vmin = null; 654 double dotMin = 1.0; 655 for (Vector3d v: principalAxesOfInertia) { 656 if (Math.abs(principalRotationVector.dot(v)) < dotMin) { 657 dotMin = Math.abs(principalRotationVector.dot(v)); 658 vmin = new Vector3d(v); 659 } 660 } 661 if (principalRotationVector.dot(vmin) < 0) { 662 vmin.negate(); 663 } 664 665 return vmin; 666 } 667 668 private static Matrix4d flipX() { 669 Matrix4d rot = new Matrix4d(); 670 rot.m00 = 1; 671 rot.m11 = -1; 672 rot.m22 = -1; 673 rot.m33 = 1; 674 return rot; 675 } 676 677 private static Matrix4d flipY() { 678 Matrix4d rot = new Matrix4d(); 679 rot.m00 = -1; 680 rot.m11 = 1; 681 rot.m22 = -1; 682 rot.m33 = 1; 683 return rot; 684 } 685 686 private static Matrix4d flipZ() { 687 Matrix4d rot = new Matrix4d(); 688 rot.m00 = -1; 689 rot.m11 = -1; 690 rot.m22 = 1; 691 rot.m33 = 1; 692 return rot; 693 } 694 695}