/**
   * Determine convex hull of two polygons, using rotating calipers method
   *
   * @param pa first polygon
   * @param pb second polygon
   * @return convex hull structure
   */
  private static PtEntry hullOfPolygons(PtEntry pa, PtEntry pb, PtEntry aHull, PtEntry bHull) {

    boolean db = C.vb(DB_INITIALHULL);

    if (db && T.update())
      T.msg(
          "construct convex hull of polygons"
              + T.show(pa, MyColor.cBLUE, STRK_THICK, -1)
              + T.show(pb, MyColor.cDARKGREEN, STRK_THICK, -1));

    PtEntry hullVertex = null;

    // A hull vertex and index
    PtEntry av = rightMostVertex(aHull);

    // B hull vertex and index
    PtEntry bv = rightMostVertex(bHull);

    double theta = Math.PI / 2;

    LineEqn aLine = new LineEqn(av, theta);

    int bSide = aLine.sideOfLine(bv);
    boolean bActive = (bSide == 0) ? (bv.y > av.y) : bSide < 0;

    if (db && T.update()) T.msg("rightmost vertices" + T.show(av) + T.show(bv));

    // construct initial vertex of hull
    hullVertex = new PtEntry(!bActive ? av : bv);

    //    if (db && T.update())
    //      T.msg("constructed initial hull vertex: " + hullVertex);

    PtEntry.join(hullVertex, hullVertex);
    PtEntry firstEnt = hullVertex;

    while (true) {
      Inf.update(inf);

      PtEntry av2 = av.next(true);
      PtEntry bv2 = bv.next(true);

      // next vertex is either A advance, B advance, or bridge
      double anga = MyMath.polarAngle(av, av2);
      double angb = MyMath.polarAngle(bv, bv2);

      double angBridge = bActive ? MyMath.polarAngle(bv, av) : MyMath.polarAngle(av, bv);

      double ta = MyMath.normalizeAnglePositive(anga - theta);
      double tb = MyMath.normalizeAnglePositive(angb - theta);
      double tc = MyMath.normalizeAnglePositive(angBridge - theta);

      // precision problem: if A and B tangent lines are parallel, both can
      // reach near zero simultaneously

      final double MAX = Math.PI * 2 - 1e-3;
      if (ta >= MAX) ta = 0;
      if (tb >= MAX) tb = 0;
      if (tc >= MAX) tc = 0;

      theta += Math.min(ta, Math.min(tb, tc));

      if (db && T.update())
        T.msg("caliper" + T.show(hullVertex) + tr(hullVertex, theta) + tp(av) + tp(bv));

      PtEntry newPoint = null;

      if (ta <= tb && ta <= tc) {
        if (db && T.update()) T.msg("A vertex is nearest" + tl(av, av2));
        // ai++;
        av = av2;
        if (!bActive) newPoint = av;
      } else if (tb <= ta && tb <= tc) {
        if (db && T.update()) T.msg("B vertex is nearest" + tl(bv, bv2));
        // bi++;
        bv = bv2;
        if (bActive) newPoint = bv;
      } else {
        if (db && T.update())
          T.msg("Bridge vertex is nearest" + tl(bActive ? bv : av, bActive ? av : bv));
        bActive ^= true;
        newPoint = bActive ? bv : av;
      }

      if (newPoint != null) {
        if (PtEntry.samePoint(newPoint, firstEnt)) {
          break;
        }

        // construct new vertex for hull of the two;
        // remember, use original vertex, not the convex hull
        hullVertex = hullVertex.insert(new PtEntry(newPoint), true);
        if (db && T.update()) T.msg("adding new caliper vertex " + T.show(hullVertex));
      }
    }
    return hullVertex;
  }
  private static void insertValleys(
      PtEntry hullPt, Object aSrc, Object bSrc) { // pa, MyPolygon pb) {
    boolean db = C.vb(DB_INSERTVALLEY);
    PtEntry ent = hullPt;

    if (db && T.update()) T.msg("insertValleys");
    do {
      PtEntry next = ent.next(true);

      if (ent.source() == next.source()) {
        PtEntry orig = ent.orig();

        if (orig.next(true) != next.orig()) {

          PtEntry vPeak0 = ent;
          PtEntry vPeak1 = next;

          EdPolygon opp = (EdPolygon) (vPeak0.source() == aSrc ? bSrc : aSrc);

          FPoint2 kernelPt = opp.getPointMod(C.vi(KERNELVERT));

          // construct a chain from the vertices of the valley
          PtEntry handle = new PtEntry(vPeak0);
          PtEntry hNext = handle;
          PtEntry e = vPeak0.orig();
          while (e != vPeak1.orig()) {
            inf.update();
            e = e.next(true);
            hNext = hNext.insert(new PtEntry(e), true);
          }

          if (C.vb(SKIPCONTOUR)) {
            PtEntry h0 = handle.next(true);
            PtEntry h1 = hNext.prev(true);
            PtEntry.join(vPeak0, h0);
            PtEntry.join(h1, vPeak1);
            if (db && T.update())
              T.msg("inserted unmodified valley" + T.show(vPeak0) + T.show(vPeak1));

          } else {
            if (!C.vb(DB_CONTOUR)) T.disable();
            PtEntry hull = COper3.buildHullForChain(handle, kernelPt);
            if (!C.vb(DB_CONTOUR)) T.enable();
            // find entries corresponding to start, end of hull
            PtEntry peak0 = null, peak1 = null;
            {
              PtEntry hEnt = hull;
              while (peak0 == null || peak1 == null) {
                inf.update();
                if (hEnt.orig() == vPeak0.orig()) peak0 = hEnt;
                if (hEnt.orig() == vPeak1.orig()) peak1 = hEnt;
                hEnt = hEnt.next(true);
              }
            }
            PtEntry.join(vPeak0, peak0.next(true));
            PtEntry.join(peak1.prev(true), vPeak1);
            if (db && T.update())
              T.msg("inserted monotonic valley" + T.show(vPeak0) + T.show(vPeak1));
          }
        }
      }
      ent = next;
    } while (ent != hullPt);
  }