/** * Determine closest point on hyperbolic arm to a point pt * * @param pt : point * @return t value for closest point; not necessarily within clipped range */ public double closestPointTo(FPoint2 pt) { final boolean db = false; pt = toCurveSpace(pt, null); double U = B + 1; double V = -pt.y; double W = B * pt.x; Polyn p = new Polyn( // B * U * U, // 2 * B * U * V, // B * V * V + A * U * U - W * W, // 2 * A * U * V, // A * V * V // ); if (db) Streams.out.println("closestPointTo, pt=" + pt + "\n" + p); double ret = 0; try { DArray r = new DArray(); if (Math.abs(p.c(0)) < 1e-5) r.addDouble(0); else p.solve(r); if (r.isEmpty()) { throw new FPError("can't find closest point, poly=\n" + p); } double bestDist = 0; for (int i = 0; i < r.size(); i++) { double t = r.getDouble(i); FPoint2 apt = calcPoint(t); double dist = apt.distance(pt); if (i == 0 || dist < bestDist) { bestDist = dist; ret = t; } } } catch (FPError e) { Tools.warn("caught FPError"); // Streams.out.println("caught:\n" + e); ret = (this.minParameter() + this.maxParameter()) * .5; } return ret; }
/** * Construct a hyperbola * * @param f1 FPoint2 : first focus * @param f2 FPoint2 : second focus * @param interceptDistance : closest distance of point on arm to f1 */ public Hyperbola(FPoint2 f1, FPoint2 f2, double interceptDistance) { double fDist = f2.distance(f1); if (!(interceptDistance >= 0 && interceptDistance <= fDist)) throw new FPError( "Hyperbola construction: icept=" + Tools.f(interceptDistance) + " of max " + Tools.f(fDist) + "\n f1=" + f1 + " f2=" + f2); double ratio = 0; if (fDist > 0) { ratio = interceptDistance / fDist; } FPoint2 pt = FPoint2.interpolate(f1, f2, ratio); construct(f1, f2, pt); }
/** * Test program for Hyperbola class * * @param args String[] */ public static void main(String[] args) { final double[] pts = { // 100, 0, -100, 0, 75, 20, // 120, 30, -100, -10, 70, 50, }; for (int i = 0; i < pts.length; i += 6) { try { Hyperbola h = new Hyperbola( new FPoint2(pts[i + 0], pts[i + 1]), new FPoint2(pts[i + 2], pts[i + 3]), new FPoint2(pts[i + 4], pts[i + 5])); System.out.println("Constructed:\n" + h); for (double t = -50; t <= 50; t += 10) { FPoint2 pt = h.calcPoint(t); FPoint2 pt2 = new FPoint2(pt.x, pt.y + 5); double tClosest = h.closestPointTo(pt2); System.out.println("t=" + Tools.f(t) + " pt=" + pt + " closest=" + tClosest); if (t == -20) { for (double t2 = tClosest - .1; t2 <= tClosest + .1; t2 += .01) { FPoint2 pt3 = h.calcPoint(t2); Streams.out.println("t2=" + t2 + " dist=" + pt3.distance(pt2)); } } } } catch (TBError e) { System.out.println(e.toString()); } } }
/** * Constructor * * @param f1 FPoint2 * @param f2 FPoint2 * @param pt FPoint2, or null for bisector */ private void construct(FPoint2 f1, FPoint2 f2, FPoint2 pt) { // userData[LEFT] = new DArray(); // userData[RIGHT] =new DArray(); final boolean db = false; if (db) { System.out.println("Hyperbola constructor\n f1=" + f1 + "\n f2=" + f2 + "\n pt=" + pt); } boolean bisector = (pt == null); initializeVisibleSegments(); // if point on arm is closer to f2 than f1, swap f1 & f2. if (!bisector && FPoint2.distanceSquared(f1, pt) > FPoint2.distanceSquared(f2, pt)) { flipped = true; } this.foci[RIGHT] = new FPoint2(f1); this.foci[LEFT] = new FPoint2(f2); if (!bisector) { this.pt = new FPoint2(pt); } double fociDist = FPoint2.distance(f1, f2); if (fociDist == 0) { throw new FPError("Hyperbola foci are same point"); } c = fociDist * .5; // calculate the translation of the hyperbola away from // standard position. FPoint2 rFocus = getFocus(0), lFocus = getFocus(1); origin = new FPoint2(.5 * (rFocus.x + lFocus.x), .5 * (rFocus.y + lFocus.y)); // calculate the angle of rotation of the hyperbola away // from the standard position. double theta = Math.atan2(rFocus.y - lFocus.y, rFocus.x - lFocus.x); Matrix fromCenterInW = Matrix.getTranslate(origin, true); Matrix rotToE = Matrix.getRotate(-theta); toE2 = rotToE; Matrix.mult(toE2, fromCenterInW, toE2); // calculate inverse toW2 = toE2.invert(null); // Matrix toCenterInW = Matrix.translationMatrix(origin, false); // Matrix rotToW = Matrix.getRotate2D(theta); // // toW2 = toCenterInW; // Matrix.mult(toW2, rotToW, toW2); // Tools.warn("just invert matrix here"); // if (bisector) { valid = true; } else { // get the arm point in hyperbola space. FPoint2 workPt = toE2.apply(pt, null); double xs = workPt.x * workPt.x; double cs = c * c; Polyn q = new Polyn(1, -(cs + xs + workPt.y * workPt.y), cs * xs); if (db) { System.out.println("a2 quadratic:\n" + q); } final DArray qsoln = new DArray(); q.solve(qsoln); if (db) { Streams.out.println(qsoln); } double val = q.c(1) * -.5; int ql = qsoln.size(); if (ql >= 1) { val = qsoln.getDouble(0); } // choose the root that is less than c*c. if (ql == 2) { if (val > qsoln.getDouble(1)) { val = qsoln.getDouble(1); if (db) { System.out.println(" two roots, choosing smaller."); } } } if (db) { System.out.println(" root chosen=" + val); } a = Polyn.sqrt(val); A = a * a; B = A / (c * c - A); } valid = true; if (db) { System.out.println(" ==> " + this); } }