/** * Calculate angle of tangent line at a particular point * * @param t : parameter * @return angle */ public double calcDirectionAt(double t) { final boolean db = false; calcTangentAt(t, workPt); double a = Math.atan2(workPt.y, workPt.x); if (db) { System.out.println("calcDirectionAt " + Tools.f(t) + " a=" + Tools.fa(a)); } return a; }
/** * Clip out a section of the arm, using internal parameter values * * @param c0 : start of section to clip out * @param c1 : end of section to clip out; if < c0, c0 & c1 are swapped */ private void clip0(double c0, double c1) { final boolean db = false; if (c0 > c1) { double t = c0; c0 = c1; c1 = t; } // array to store new clip elements in DArray nc = new DArray(); if (db) { System.out.println("clip " + Tools.f(c0) + "..." + Tools.f(c1)); } for (int i = 0; i < visSeg.size(); i += 2) { double t0 = visSeg.getDouble(i), t1 = visSeg.getDouble(i + 1); if (db) { System.out.println(" seg " + i + " " + Tools.f(t0) + "..." + Tools.f(t1)); } // if this seg is outside clip region, leave intact (add it) if (t0 >= c1 || t1 <= c0) { nc.addDouble(t0); nc.addDouble(t1); continue; } if (t0 >= c0 && t1 <= c1) { if (db) { System.out.println(" clipping entire segment"); } // remove entire segment, by skipping it continue; } // add unclipped portion if (t0 < c0) { nc.addDouble(t0); nc.addDouble(c0); } if (t1 > c1) { nc.addDouble(c1); nc.addDouble(t1); } } visSeg = nc; if (db) { System.out.println(" after clipping:" + visSeg); } }
public void render(Color c, int stroke, int markType) { if (c == null) c = Color.RED; V.pushColor(c); if (defined()) { seg.render(c, stroke, markType); } else { for (int i = 0; i < 2; i++) { if (i == 1 && discA == discB) continue; EdObject obj = object(i); if (obj instanceof EdDisc) { EdDisc d = (EdDisc) obj; stroke = STRK_RUBBERBAND; V.pushStroke(stroke); double r = Math.max(d.getRadius() - 4, 2.0); V.drawCircle(d.getOrigin(), r); V.popStroke(); } else { Tools.unimp("rendering undefined bitangents of polygons"); } } } V.popColor(); }
/** * 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); }
/** * 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; }
public String visString() { StringBuilder sb = new StringBuilder(); if (visSeg != null) { for (int i = 0; i < visSeg.size(); i += 2) { double t0 = 0, t1 = 0; if (flipped()) { int j = visSeg.size() - i - 2; t0 = toExt(visSeg.getDouble(j + 1)); t1 = toExt(visSeg.getDouble(j)); } else { t0 = visSeg.getDouble(i); t1 = visSeg.getDouble(i + 1); } sb.append("<"); sb.append(Tools.f(t0)); sb.append(".."); sb.append(Tools.f(t1)); sb.append("> "); } } return sb.toString(); }
/** * 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()); } } }
/** * Calculate a point on the hyperbola * * @param t : parameter in internal space (after flipping has occurred) * @param dest : where to store the calculated point */ private FPoint2 calcPointInternal(double t, FPoint2 dest) { final boolean db = false; if (!valid) throw new FPError("calcPoint of invalid hyperbola"); // Tools.ASSERT(valid, "calcPoint of invalid hyperbola"); // final FPoint2 work = new FPoint2(); // // work.setLocation(Polyn.sqrt(A + t * t * B), t); dest = toW2.apply(Polyn.sqrt(A + t * t * B), t, dest); // // if (dest == null) // dest = new FPoint2(); // Matrix.apply(toW2, work, dest); if (db) { System.out.println( "calcPoint t=" + Tools.f(toExt(t)) // + " armsp=" + work + " world=" + dest); } return dest; }
private void construct(EdDisc a, EdDisc b) { this.discA = a; this.discB = b; if (EdDisc.partiallyDisjoint(a, b)) { // if (!UHullMain.oldBitanMethod()) { final boolean db = false; if (a.getRadius() == b.getRadius()) { FPoint2 oa = a.getOrigin(), ob = b.getOrigin(); FPoint2 n = new FPoint2(-(ob.y - oa.y), ob.x - oa.x); n.normalize(); n.x *= a.getRadius(); n.y *= a.getRadius(); seg = new DirSeg(FPoint2.add(oa, n, null), FPoint2.add(ob, n, null)); return; } boolean swap = a.getRadius() > b.getRadius(); if (swap) { b = (EdDisc) discA; a = (EdDisc) discB; } if (db && T.update()) T.msg( "BiTangent construct, arad=" + Tools.f(a.getRadius()) + " brad=" + Tools.f(b.getRadius()) + " swap=" + swap + " origin.a=" + T.show(a.getOrigin())); FPoint2 oa = a.getOrigin(); FPoint2 ob = b.getOrigin(); double U = ob.x, V = ob.y; double A = oa.x - U, B = oa.y - V; double R1 = a.getRadius(); double R2 = b.getRadius(); double S = R2 - R1; double x1, y1, x2, y2; x1 = A; y1 = B; boolean secondRoot; boolean altSlope = Math.abs(B) < Math.abs(A); if (!altSlope) { double C1 = S * S / B, C2 = -A / B; double qA = 1 + C2 * C2, qB = 2 * C1 * C2, qC = C1 * C1 - S * S; double root = Math.sqrt(qB * qB - 4 * qA * qC); x2 = (-qB - root) / (2 * qA); y2 = C1 + C2 * x2; secondRoot = MyMath.sideOfLine(x2, y2, A, B, 0, 0) < 0; if (swap ^ secondRoot) { x2 = (-qB + root) / (2 * qA); y2 = C1 + C2 * x2; } } else { double C1 = S * S / A, C2 = -B / A; double qA = 1 + C2 * C2, qB = 2 * C1 * C2, qC = C1 * C1 - S * S; double root = Math.sqrt(qB * qB - 4 * qA * qC); y2 = (-qB - root) / (2 * qA); x2 = C1 + C2 * y2; secondRoot = MyMath.sideOfLine(x2, y2, A, B, 0, 0) < 0; if (swap ^ secondRoot) { y2 = (-qB + root) / (2 * qA); x2 = C1 + C2 * y2; } } // now grow both discs back to r1, r2 double tx = U; double ty = V; // if (S == 0) { // FPoint2 unit = new FPoint2(-A, -B); // if (swap) { // unit.x = -unit.x; // unit.y = -unit.y; // } // unit.normalize(); // tx += -unit.y * R1; // ty += unit.x * R1; // } else { double F = R1 / S; tx += x2 * F; ty += y2 * F; } if (db && T.update()) T.msg("adding offset to both points: " + tx + ", " + ty + T.show(new FPoint2(tx, ty))); x1 += tx; y1 += ty; x2 += tx; y2 += ty; FPoint2 p1 = new FPoint2(x1, y1); FPoint2 p2 = new FPoint2(x2, y2); if (swap) { FPoint2 tmp = p1; p1 = p2; p2 = tmp; } seg = new DirSeg(p1, p2); if (db && T.update()) T.msg( "swap=" + swap + " altSlope=" + altSlope + " secondRoot=" + secondRoot + " dirseg=" + EdSegment.showDirected(p1, p2)); } // else { // // double th = calcTheta(a, b); // LineEqn eqn = new LineEqn(a.polarPoint(th + Math.PI / 2), th); // double ta = eqn.parameterFor(a.getOrigin()); // double tb = eqn.parameterFor(b.getOrigin()); // seg = new DirSeg(eqn.pt(ta), eqn.pt(tb)); // // } } }
/** * Construct a string * * @param javaMode : if true, generates java code to construct it * @return String */ private String toString(boolean javaMode) { StringBuilder sb = new StringBuilder(); // sb.setLength(0); if (javaMode) { sb.append( "new Hyperbola(new FPoint2(" + foci[RIGHT].x + "," + foci[RIGHT].y + "), new FPoint2(" + foci[LEFT].x + "," + foci[LEFT].y + "), new FPoint2(" + pt.x + "," + pt.y + "));"); } else { sb.append("hyperbola: "); if (!valid) { sb.append("***INVALID***"); } else { sb.append( "f(r)=" + foci[RIGHT] + " f(l)=" + foci[LEFT] + " a=" + Tools.f(a) + " c=" + Tools.f(c)); sb.append(" flip=" + Tools.f(flipped())); if (visSeg != null) { sb.append("\nVisible segments: "); for (int i = 0; i < visSeg.size(); i += 2) { double t0 = 0, t1 = 0; if (flipped()) { int j = visSeg.size() - i - 2; t0 = toExt(visSeg.getDouble(j + 1)); t1 = toExt(visSeg.getDouble(j)); } else { t0 = visSeg.getDouble(i); t1 = visSeg.getDouble(i + 1); } sb.append("<"); sb.append(Tools.f(t0)); sb.append(".."); sb.append(Tools.f(t1)); sb.append("> "); } sb.append("\n"); } } } return sb.toString(); }
/** * Find intersections of hyperbolic arm with an axes-aligned line segment. Arm must intersect * segment, not its underlying (infinite) line. The intersection points are sorted into increasing * parameter values w.r.t. the arm. * * @param s0 : start of line segment * @param s1 : end of line segment * @param ipts : intersection points returned here * @param reverseOrder : if true, points are sorted by decreasing parameter values * @param dbFlag : true to print debug information */ public void findOrthogonalIntersect( FPoint2 s0, FPoint2 s1, DArray ipts, boolean reverseOrder, boolean dbFlag) { final boolean db = true && dbFlag; if (db) { System.out.println( "findOrthogonalIntersect " + s0 + " -> " + s1 + " rev:" + Tools.f(reverseOrder)); } // Determine whether this is a vertical or horizontal line segment. boolean vert = (Math.abs(s1.y - s0.y) > Math.abs(s1.x - s0.x)); if (db) { System.out.println(" vert=" + Tools.f(vert)); } // Find the quadratic to solve. Polyn p; PlaneCurve cv = getCurve(); if (vert) { p = cv.solveForX(s0.x); } else { p = cv.solveForY(s0.y); } DArray lst = new DArray(); p.solve(lst); if (db) { System.out.println(" curve=" + cv.toString(true)); System.out.println(" polyn=" + p.toString(true)); System.out.println(" roots=" + Tools.d(lst)); } // Sort points, discarding those not on line segment, // and those not on the correct arm. ipts.clear(); for (int i = 0; i < lst.size(); i++) { double ta = lst.getDouble(i); FPoint2 pt; if (vert) { pt = new FPoint2(s0.x, ta); } else { pt = new FPoint2(ta, s0.y); } if (db) { System.out.println(" position on arm for ta=" + ta + " is " + pt); } double t = MyMath.positionOnSegment(pt, s0, s1); if (db) { System.out.println(" pt=" + pt + " t=" + t); } if (t < 0 || t > 1) { if (db) { System.out.println(" not on segment, skipping"); } continue; } FPoint2 cpt = toCurveSpace(pt, null); if (db) { System.out.println(" curveSpace=" + cpt); } if (cpt.x < 0) { if (db) { System.out.println(" skipping..."); } continue; } double t1 = calcParameter(pt); int j = 0; while (true) { if (j == ipts.size()) { break; } double t2 = calcParameter(ipts.getFPoint2(j)); if (!reverseOrder) { if (t1 < t2) { break; } } else if (t1 > t2) { break; } j++; } ipts.add(j, pt); } if (db) { System.out.println(" ipts=" + Tools.d(ipts)); } }
public void render(Color c, int stroke, int markType) { final boolean db = false; // Get array of visible segments. If no such array exists, // use default. DArray vseg = visSeg; boolean dashed = false; // if (step == 0) { double step = renderStep(); // } // vp V = TestBed.view(); if (db) Streams.out.println(" step=" + step); // plot each visible segment for (int seg = 0; seg < vseg.size(); seg += 2) { double t0 = vseg.getDouble(seg + 0), t1 = vseg.getDouble(seg + 1); t0 = MyMath.clamp(t0, -500.0, 500.0); t1 = MyMath.clamp(t1, -500.0, 500.0); // render() expects external parameters. double s0 = toExt(t0), s1 = toExt(t1); if (s0 > s1) { double tmp = s0; s0 = s1; s1 = tmp; } FPoint2 p0 = calcPoint(s0), p1 = calcPoint(s1); if (db) Streams.out.println(" p0=" + p0 + ", p1=" + p1); if (isLine() && !dashed) { V.drawLine(p0, p1); } else { /* if (Math.abs(s0) >= 500 ||Math.abs(s1) >= 500) System.out.println("Rendering "+t0+" to "+t1+" step "+step); */ if (dashed) V.pushStroke(Globals.STRK_RUBBERBAND); { // int count = 0; boolean first = true; for (double t = t0; ; t += step) { // , count++) { boolean last = (t >= t1); if (last) { t = t1; } calcPointInternal(t, p1); if (!p1.isValid()) { if (last) { break; } continue; } if (db) { System.out.println(" calcPt " + Tools.f(toExt(t)) + " = " + p1.x + "," + p1.y); } if (!first) { V.drawLine(p0, p1); if (false) { Tools.warn("highlighting int"); V.mark(p0); } } if (last) { break; } p0.setLocation(p1); first = false; } } if (dashed) V.popStroke(); } } }