/**
   * Convert array of hyperbolas to hyperbolas with single connected range
   *
   * @param h1
   * @return
   */
  public static Hyperbola[] singlify(Hyperbola[] h1) {
    DArray e = new DArray();
    for (int i = 0; i < h1.length; i++) {
      Hyperbola hi = h1[i];

      if (hi.visSeg.size() == 1) {
        e.add(hi);
      } else {
        for (int j = 0; j < hi.visSeg.size(); j += 2) {
          double t0 = hi.visSeg.getDouble(j + 0);
          double t1 = hi.visSeg.getDouble(j + 1);
          Hyperbola h = new Hyperbola(hi);
          h.visSeg.clear();
          h.visSeg.addDouble(t0);
          h.visSeg.addDouble(t1);
          e.add(h);
        }
      }
    }
    return (Hyperbola[]) e.toArray(Hyperbola.class);
  }
  /**
   * Calc possible hull from a set of polygons
   *
   * @param polySet set of polygons
   * @param first first polygon to include
   * @param last last polygon to include
   * @return polygon containing possible hull
   */
  private static EdPolygon calcPHullRange(EdPolygon[] polySet, int first, int last) {
    final boolean db = true;
    int len = last + 1 - first;
    if (len == 1) return polySet[first];
    if (len > 2) {
      if (db && T.update()) {
        DArray a = new DArray();
        for (int i = first; i <= last; i++) a.add(polySet[i]);
        T.msg("calc possible hull of multiple polygons" + T.show(a, MyColor.cRED, STRK_THICK, -1));
      }

      int n = len / 2;
      return calcPHull(
          calcPHullRange(polySet, first, first + n - 1), calcPHullRange(polySet, first + n, last));
    }

    return calcPHull(polySet[first], polySet[first + 1]);
  }
  /**
   * Find intersections of hyperbolic arm with a line.
   *
   * @param s0 : first point on line
   * @param s1 : second point on line
   * @param ipts : intersection points returned here
   */
  public DArray findLineIntersect(FPoint2 s0, FPoint2 s1, DArray ipts) {
    if (ipts == null) ipts = new DArray();
    ipts.clear();

    // transform both line points to curve space

    FPoint2 c0 = toCurveSpace(s0, null), c1 = toCurveSpace(s1, null);

    double a = c0.x, b = c0.y, c = c1.x, d = c1.y;

    double e = d - b;
    double f = c - a;

    double D = f * f - B * e * e;
    double E = 2 * a * f - 2 * b * B * e;
    double F = a * a - A - B * b * b;
    Polyn q = new Polyn(D, E, F);

    DArray roots = new DArray();
    q.solve(roots);

    for (int i = 0; i < roots.size(); i++) {
      double k = roots.getDouble(i);
      FPoint2 pt =
          //      ipts.add(
          new FPoint2(s0.x + (s1.x - s0.x) * k, s0.y + (s1.y - s0.y) * k);
      //      );

      // Make sure this point is actually on the arm.
      FPoint2 cpt = toCurveSpace(pt, null);
      if (cpt.x < 0) {
        continue;
      }
      ipts.add(pt);
    }
    return ipts;
  }
  /**
   * 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));
    }
  }
  /**
   * Find intersections between two hyperbolas
   *
   * @param a Hyperbola
   * @param b Hyperbola
   * @param iPts where to store intersection points; null to construct
   */
  public static DArray findIntersections(Hyperbola a, Hyperbola b, DArray iPts) {
    final boolean db = false;

    if (iPts == null) iPts = new DArray();
    iPts.clear();
    final DArray jPts = new DArray();
    // a.initClipIfNec();
    //    b.initClipIfNec();
    if (db) {
      System.out.println(
          "finding intersections between:\n" + a.toString(true) + "\n" + b.toString(true));
    }

    PlaneCurve.findIntersect(a.getCurve(), b.getCurve(), jPts);

    // filter out false intersections
    if (db) {
      System.out.println("prefilter # intersections= " + jPts.size());
    }

    FPoint2 ptc = new FPoint2();

    for (int i = 0; i < jPts.size(); i++) {
      FPoint2 pt = jPts.getFPoint2(i);

      // make sure calculated intersect point is actually on both arms
      {
        FPoint2 data = a.calcParameterAndDistance(pt);
        if (data.y > .001) continue;
        data = b.calcParameterAndDistance(pt);
        if (data.y > .001) continue;
      }

      if (!a.isLine()) {
        // put in curve space to verify it's to the right of the y axis
        a.toCurveSpace(pt, ptc);
        if (db) {
          System.out.println("Filter " + i + " in curveA= " + ptc);
        }
        if (ptc.x <= 0) {
          if (db) {
            System.out.println(" < 0");
          }
          continue;
        }
      }
      if (!b.isLine()) {
        b.toCurveSpace(pt, ptc);
        if (db) {
          System.out.println("        in curveB= " + ptc);
        }
        if (ptc.x <= 0) {
          if (db) {
            System.out.println(" < 0");
          }
          continue;
        }
      }
      if (db) {
        System.out.println(" adding " + pt);
      }
      iPts.add(pt);
    }
    return iPts;
  }
  private static void OLDexpandHull(
      PtEntry convHullEntry, PtEntry aHull, PtEntry bHull, boolean ccw) {
    boolean db__OLD = C.vb(DB_HULLEXPAND);
    if (db__OLD && T.update()) T.msg("expandHull" + T.show(convHullEntry) + " ccw=" + ccw);

    PtEntry[] opp = new PtEntry[2];
    opp[0] = aHull;
    opp[1] = bHull;

    PtEntry old____hEnt = convHullEntry;
    boolean advanced = false;
    do {
      if (old____hEnt != convHullEntry) advanced = true;
      inf.update();
      if (old____hEnt.source() == null) {
        if (db__OLD && T.update())
          T.msg("expandHull, source unknown, guaranteed not convex" + T.show(old____hEnt));
        old____hEnt = old____hEnt.next(ccw);
        continue;
      }

      int w = (old____hEnt.source() == opp[0].source()) ? 1 : 0;
      PtEntry oppEnt = opp[w];

      boolean isTangent =
          !COper3.right(old____hEnt, oppEnt, oppEnt.next(true), ccw)
              && !COper3.right(old____hEnt, oppEnt, oppEnt.prev(true), ccw);

      if (!isTangent) {
        if (db__OLD && T.update())
          T.msg(
              "expandHull, advance tangent line"
                  + T.show(oppEnt.toPolygon(), MyColor.cDARKGRAY, -1, MARK_X)
                  + T.show(old____hEnt)
                  + tl(old____hEnt, oppEnt));
        opp[w] = oppEnt.next(ccw);
        continue;
      }

      if (COper3.left(old____hEnt, oppEnt, old____hEnt.next(ccw), ccw)
          && COper3.left(old____hEnt, oppEnt, old____hEnt.prev(ccw), ccw)) {
        DArray dispPts = new DArray();

        // delete points until cross tangent line
        PtEntry next = old____hEnt.next(ccw);
        while (true) {
          PtEntry prev = next;
          dispPts.add(prev);
          next = prev.delete(ccw);

          inf.update();
          if (COper3.right(old____hEnt, oppEnt, next, ccw)) {
            FPoint2 cross = MyMath.linesIntersection(old____hEnt, oppEnt, prev, next, null);
            old____hEnt = old____hEnt.insert(new PtEntry(cross), ccw);
            if (db__OLD && T.update())
              T.msg(
                  "expandHull, clipped to shadow region"
                      + tl(old____hEnt, oppEnt)
                      + T.show(old____hEnt)
                      + T.show(dispPts));
            break;
          }
        }
      } else {
        if (db__OLD && T.update())
          T.msg(
              "expandHull, not dipping into shadow region"
                  + T.show(old____hEnt.next(ccw))
                  + tl(old____hEnt, oppEnt));
      }
      old____hEnt = old____hEnt.next(ccw);
    } while (!advanced || old____hEnt != convHullEntry);
  }
  /**
   * Apply hull expansion procedure
   *
   * @param convHullEntry an entry of the hull (should be on the convex hull, so it is not deleted
   *     or replaced and is still valid for subsequent calls)
   * @param aHull entry on convex hull of polygon A
   * @param bHull entry on convex hull of polygon B
   * @param ccw true to move in ccw direction, else cw
   */
  private static void expandHull(PtEntry convHullEntry, PtEntry aHull, PtEntry bHull, boolean ccw) {
    if (C.vb(OLDMETHOD)) {
      OLDexpandHull(convHullEntry, aHull, bHull, ccw);
      return;
    }
    boolean db = C.vb(DB_HULLEXPAND);

    if (db && T.update()) T.msg("expandHull" + T.show(convHullEntry) + " ccw=" + ccw);

    // tangent points for A, B
    PtEntry[] tangentPoints = new PtEntry[2];
    tangentPoints[0] = aHull;
    tangentPoints[1] = bHull;

    PtEntry hEnt = convHullEntry;
    do {
      inf.update();

      // calculate tangent ray R
      PtEntry tangentPt = null;
      while (true) {
        int tanIndex = (hEnt.source() == tangentPoints[0].source()) ? 1 : 0;
        tangentPt = tangentPoints[tanIndex];

        if (!COper3.right(hEnt, tangentPt, tangentPt.next(true), ccw)
            && !COper3.right(hEnt, tangentPt, tangentPt.prev(true), ccw)) break;

        tangentPt = tangentPt.next(ccw);

        if (db && T.update())
          T.msg(
              "expandHull, advance tangent line"
                  + T.show(tangentPt.toPolygon(), MyColor.cDARKGRAY, -1, MARK_DISC)
                  + T.show(hEnt)
                  + tl(hEnt, tangentPt));
        tangentPoints[tanIndex] = tangentPt;
      }

      if (COper3.left(hEnt, tangentPt, hEnt.next(ccw), ccw)) {
        DArray dispPts = new DArray();

        // delete points until cross tangent line
        PtEntry next = hEnt.next(ccw);
        while (true) {
          PtEntry prev = next;
          dispPts.add(prev);
          next = prev.delete(ccw);
          if (COper3.right(hEnt, tangentPt, next, ccw)) {
            FPoint2 cross = MyMath.linesIntersection(hEnt, tangentPt, prev, next, null);
            hEnt = hEnt.insert(new PtEntry(cross), ccw);
            if (db && T.update())
              T.msg(
                  "expandHull, clipped to shadow region"
                      + tl(hEnt, tangentPt)
                      + T.show(hEnt)
                      + T.show(dispPts));
            break;
          }
        }
      } else {
        if (db && T.update())
          T.msg(
              "expandHull, not dipping into shadow region"
                  + T.show(hEnt.next(ccw))
                  + tl(hEnt, tangentPt));
      }
      while (true) {
        hEnt = hEnt.next(ccw);
        if (COper3.left(hEnt.prev(ccw), hEnt, hEnt.next(ccw), ccw)) break;
        if (db && T.update()) T.msg("skipping reflex vertex" + T.show(hEnt));
      }
    } while (hEnt != convHullEntry);
  }