/** * Returns the result of rotating a matrix about a given unit vector by a given angle. * * @param matrix initial matrix * @param unitvect vector to rotate around, must be normalised * @param angle rotation angle in radians * @return rotated matrix */ private static double[] rotateAround(double[] matrix, double[] unitvec, double angle) { assert Math.abs( (unitvec[0] * unitvec[0] + unitvec[1] * unitvec[1] + unitvec[2] * unitvec[2]) - 1) < 1e6; double[] rm = Matrices.fromPal(new Pal().Dav2m(Matrices.mult(unitvec, angle))); return Matrices.mmMult(matrix, rm); }
/** * Takes X,Y,Z normalised coordinate ranges and tries to work out the ranges in projected plane * coordinates they represent for a given rotation matrix. The determination is approximate. * * @param vxyzRanges 3-element array giving ranges of normalised X,Y,Z data coordinates * @param rot 9-element rotation matrix to be applied before projection * @return 2-element array giving plane coordinate X and Y ranges covered by the supplied data * coordinate ranges */ private Range[] readProjectedRanges(Range[] vxyzRanges, double[] rot) { double[] vxBounds = vxyzRanges[0].getBounds(); double[] vyBounds = vxyzRanges[1].getBounds(); double[] vzBounds = vxyzRanges[2].getBounds(); double[] r3 = new double[3]; Range pxRange = new Range(); Range pyRange = new Range(); /* Iterate over each corner of the cuboid represented by the XYZ * ranges, and use each of these corners (actually, their projection * on the unit sphere surface) as sample points to mark out the * limits of X and Y projected position ranges. * This is pretty rough, but should provide an overestimate of the * actual X, Y ranges (I think?). The smaller the range in XYZ * the better the estimate. */ Point2D.Double point = new Point2D.Double(); for (int jx = 0; jx < 2; jx++) { r3[0] = vxBounds[jx]; for (int jy = 0; jy < 2; jy++) { r3[1] = vyBounds[jy]; for (int jz = 0; jz < 2; jz++) { r3[2] = vzBounds[jz]; double[] s3 = Matrices.normalise(Matrices.mvMult(rot, r3)); if (project(s3[0], s3[1], s3[2], point)) { pxRange.submit(point.x); pyRange.submit(point.y); } } } } return new Range[] {pxRange, pyRange}; }
/** * Attempts to return a rotation matrix corresponding to moving the plane between two cursor * positions, with a given initial rotation matrix in effect. * * @param rot0 initial rotation matrix * @param pos0 initial projected position * @param pos1 destination projected position * @return destination rotation matrix, or null * @see Projection#projRotate */ private double[] genericRotate(double[] rot0, Point2D.Double pos0, Point2D.Double pos1) { double[] rv0 = new double[3]; if (unproject(pos0, rv0) && getSkyviewProjecter().validPosition(new double[] {pos1.x, pos1.y})) { double[] unrot0 = Matrices.invert(rot0); double[] ru0 = Matrices.mvMult(unrot0, rv0); return getRotation(ru0, pos1, rot0); } else { return null; } }
private static double[] getRotation(double[] rv0, Point2D.Double pos1, double[] rot0) { boolean reflect = isReflected(rot0); final double fr = reflect ? -1 : +1; final double rx = rv0[0]; final double ry = rv0[1]; final double rz = rv0[2]; final double px = pos1.x; final double py = pos1.y; // Use algebra from verticalRotate matrix. double delta0 = Math.atan2(-rot0[2], rot0[8]); double alpha0 = Math.atan2(-rot0[3], rot0[4] * fr); // Find alpha and delta rotation angles which put the given vector // at the target screen position. double alpha = new Solver() { double[] derivs(double a) { double sa = Math.sin(a); double ca = Math.cos(a); return new double[] { -sa * rx + ca * ry * fr - px, -ca * rx - sa * ry * fr, }; } }.solve(alpha0); if (Double.isNaN(alpha)) { return null; } final double ca = Math.cos(alpha); final double sa = Math.sin(alpha); double delta = new Solver() { double[] derivs(double d) { double sd = Math.sin(d); double cd = Math.cos(d); return new double[] { sd * (ca * rx + sa * ry * fr) + cd * rz - py, cd * (ca * rx + sa * ry * fr) - sd * rz, }; } }.solve(delta0); if (Double.isNaN(delta)) { return null; } double[] rot1 = verticalRotate(delta, alpha, reflect); if (Matrices.mvMult(rot1, rv0)[0] >= 0 && Matrices.mvMult(rot1, RZ)[2] > 0) { return rot1; } else { return null; } }
/** * Works out the rotation matrix to use to center the positions represented by Cartesian * coordinate ranges on the plot surface. Null may be returned if there is no appropriate * rotation. * * @return 9-element rotation matrix or null. */ private static double[] getRangeRotation(Range[] vxyzRanges, boolean reflect) { double[] center = new double[3]; for (int id = 0; id < 3; id++) { double[] bounds = vxyzRanges[id].getBounds(); double mid = 0.5 * (bounds[0] + bounds[1]); if (Double.isNaN(mid)) { return null; } assert mid >= -1.001 && mid <= +1.001; center[id] = mid; } if (Matrices.mod(center) < 0.3) { return null; } center = Matrices.normalise(center); return rotateToCenter(center, reflect); }
@Override public double[] cursorRotate(double[] rot0, Point2D.Double pos0, Point2D.Double pos1) { /* Attempt the rotation that transforms a point from one * projected plane position to another. */ double[] rot1 = genericRotate(rot0, pos0, pos1); if (rot1 != null) { return rot1; } /* That may fail because one or other of the supplied points is * not in the projection region. In that case do something * that feels like dragging the sphere around. * This rotation could be improved. It is algebraically messy, * and it also does not transition smoothly from the genericRotate * case, though perhaps that's not possible in general. */ else { boolean reflect = isReflected(rot0); double fr = reflect ? -1 : +1; double phi = (pos1.x - pos0.x); double psi = (pos1.y - pos0.y); double[] rm = rot0; double[] sightvec = Matrices.mvMult(Matrices.invert(rm), new double[] {1, 0, 0}); double[] hvec = Matrices.normalise(Matrices.cross(sightvec, RZ)); rm = rotateAround(rm, hvec, -psi); rm = rotateAround(rm, RZ, -phi * fr); if (Matrices.mvMult(rm, RZ)[2] >= 0) { return rm; } else { double delta = Math.atan2(-rm[2], rm[8]); double alpha = Math.atan2(-rm[3], rm[4] * fr); delta = Math.min(+0.5 * Math.PI, delta); delta = Math.max(-0.5 * Math.PI, delta); return verticalRotate(delta, alpha, reflect); } } }
/** * Indicates whether a rotation matrix represents reflected coordinates. * * @param rotmat rotation matrix * @return true if determinant is less than 0 */ private static boolean isReflected(double[] rotmat) { return Matrices.det(rotmat) < 0; }