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; } }
/** * 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; } }
@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); } } }