def zip_hulls(base, triang):
    """
    Given a triangulation containing two seperate hulls and the base edge 
    connecting the hulls, triangulate the space between the hulls. This is
    refered to as 'zipping' the hulls together.
    
    Parameters
    ----------
    base : int
        index of base edge
    triang : TriangulationEdges 
        Incomplete triangulation, with known base edge

    Returns
    -------
    d_triang : TriangulationEdges 
        Instance of TriangulationEdges class object containing the finished
        Delaunay triangulation of the input triangulation. 
    """
    while True:
        # Make variables for commonly used base edge points
        base1 = triang.points[triang.edges[base].org]
        base2 = triang.points[triang.edges[base].dest]

        # Find the first candidate edges for triangulation from each subset
        rcand = triang.edges[triang.edges[base].sym].onext
        pt1 = triang.points[triang.edges[rcand].dest]
        rcand_valid = linalg.on_right(base1, base2, pt1)

        lcand = triang.edges[base].oprev
        pt2 = triang.points[triang.edges[lcand].dest]
        lcand_valid = linalg.on_right(base1, base2, pt2)

        # If neither candidate is valid, hull merge is complete
        if not rcand_valid and not lcand_valid:
            break

        if rcand_valid:
            triang, rcand = rcand_func(triang, rcand, base1, base2)

        if lcand_valid:
            triang, lcand = lcand_func(triang, lcand, base1, base2)

        lcand_strong_valid = candidate_decider(rcand, lcand, lcand_valid,
                                               triang)

        if not rcand_valid or lcand_strong_valid:
            base = triang.connect(lcand, triang.edges[base].sym)
        else:
            base = triang.connect(triang.edges[base].sym,
                                  triang.edges[rcand].sym)
    return triang
def triangle_primitive(pts_subset):
    """
    This function takes a list of three points and forms three edges to 
    create a single triangle. This triangle has the property that the origin
    of one edge is connected to the destination of the next edge in a CCW 
    orientation.

    Parameters
    ----------
    pts_index : list
        List of the indices of the three points
    pts_subset : lists of lists
        A set of three points with the form [ [x1, y1], [x2, y2] , [x3, y3] ]

    Returns
    -------
    out1 : int
        Index of edge with the left most point
    ou2 : int
       Index of the edge with the right most point
    edges : TriangulationEdges
        The resulting triangulation of three points
    """
    p1, p2, p3 = 0, 1, 2
    triang = edge_topology.TriangulationEdges(pts_subset)

    # Create the first two edges of the triangle
    edge1, edge1_sym = edge_topology.setup_edge(p1, p2, 0)
    triang.push_back(edge1)
    triang.push_back(edge1_sym)

    edge2, edge2_sym = edge_topology.setup_edge(p2, p3, 2)
    triang.push_back(edge2)
    triang.push_back(edge2_sym)

    triang.splice(edge1_sym.index, edge2.index)

    # To maintain the counter-clockwise orientation of the edges in the
    # triangle, we determine where p3 is in relation to the two existing edges.
    pt1 = pts_subset[triang.edges[edge1.index].org]
    pt2 = pts_subset[triang.edges[edge1.index].dest]
    pt3 = pts_subset[p3]

    if linalg.on_right(pt1, pt2, pt3):
        # Points are in CCW orientiaton
        c = triang.connect(edge2.index, edge1.index)
        triang.set_extreme_edges(edge1.index, edge2_sym.index)
        return triang

    if linalg.on_left(pt1, pt2, pt3):
        # Points are in CW orientiaton
        c = triang.connect(edge2.index, edge1.index)
        triang.set_extreme_edges(triang.edges[c].sym, c)
        return triang

    # Points are collinear
    triang.set_extreme_edges(edge1.index, edge2_sym.index)
    return triang
def lowest_common_tangent(h_left, h_right):
    """
    Given two fully triangulated sets of points, this function finds an
    edge connecting the two triangulations. Each triangulation forms a convex 
    hull of edges. The edge to be found by this function is the edge with the 
    lowest y-value point which is still tangential to both hulls. This is
    known as the 'base' edge, as it is the first edge connecting two 
    separately triangulated point sets. 

    Parameters
    ----------
    h_left : TriangulationEdges
    h_right : TriangulationEdges

    Returns
    -------
    left_e : int
        The index of the edge in the right hull which forms one end of the 
        base edge
    right_e : int
        The index of the edge in the left hull which forms the other end of 
        the base edge
    """
    left_e = h_left.outer
    right_e = h_right.inner

    pts_left = h_left.points
    pts_right = h_right.points

    p1 = pts_left[h_left.edges[left_e].org]
    p2 = pts_left[h_left.edges[left_e].dest]

    p4 = pts_right[h_right.edges[right_e].org]
    p5 = pts_right[h_right.edges[right_e].dest]

    while True:
        if linalg.on_right(p1, p2, pts_right[h_right.edges[right_e].org]):
            left_e = h_left.edges[h_left.edges[left_e].sym].onext

            p1 = pts_left[h_left.edges[left_e].org]
            p2 = pts_left[h_left.edges[left_e].dest]

        elif linalg.on_left(p4, p5, pts_left[h_left.edges[left_e].org]):
            right_e = h_right.edges[h_right.edges[right_e].sym].oprev
            p4 = pts_right[h_right.edges[right_e].org]
            p5 = pts_right[h_right.edges[right_e].dest]

        else:
            return left_e, right_e
def lcand_func(lhull, lcand, b1, b2):
    """
    This function performs the same task as the above 'rcand_func' but testing
    for the left candidate edge. 
    """
    completed = False
    while not completed:
        lcand_oprev_dest = lhull.edges[lhull.edges[lcand].oprev].dest
        lcand_dest = lhull.edges[lcand].dest
        ccw_test = linalg.on_right(b1, b2, lhull.points[lcand_oprev_dest])
        next_cand_invalid = linalg.in_circle(b2, b1, lhull.points[lcand_dest],
                                             lhull.points[lcand_oprev_dest])
        if ccw_test and next_cand_invalid:
            t = lhull.edges[lcand].oprev
            lhull.kill_edge(lcand)
            lcand = t
        else:
            completed = True
    return lhull, lcand
def rcand_func(rhull, rcand, b1, b2):
    """
    This function finds the candidate edge from the right hull triangulation.
    An initial candidate 'rcand' is given. This candidate is tested. If the
    candidate fails it is deleted from the triangulation and the next 
    potential candiate is considered. While a valid candidate has not been 
    found this process continues until a valid candidate is found.

    Parameters
    ----------
    rhull : TriangulationEdges
        The triangulation of edges on the right hand side
    rcand : TYPE
        DESCRIPTION.
    b1 : list
        DESCRIPTION.
    b2 : list
        DESCRIPTION.

    Returns
    -------
    rhull : TriangulationEdges
        DESCRIPTION.
    rcand : TYPE
        DESCRIPTION.
    """
    completed = False
    while not completed:
        rcand_onext_dest = rhull.edges[rhull.edges[rcand].onext].dest
        rcand_dest = rhull.edges[rcand].dest
        ccw_test = linalg.on_right(b1, b2, rhull.points[rcand_onext_dest])
        next_cand_invalid = linalg.in_circle(b2, b1, rhull.points[rcand_dest],
                                             rhull.points[rcand_onext_dest])
        if ccw_test and next_cand_invalid:
            t = rhull.edges[rcand].onext
            rhull.kill_edge(rcand)
            rcand = t
        else:
            completed = True
    return rhull, rcand