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