コード例 #1
0
ファイル: initialize.py プロジェクト: bmmeijers/grassfire
def find_overlapping_triangle(E):
    """find overlapping triangle 180 degrees other way round
    of triangle edge of the first triangle

    it assumes that the edges are around a central vertex
    and that the first triangle has a constrained edge and other edges are not.

    returns index in the list of the triangle that overlaps
    """
    first = E[0]
    t = first.triangle
    mid = first.side
    begin = ccw(mid)
    P, Q = t.vertices[begin], t.vertices[mid]
    overlap = None
    idx = None
    for i, e in enumerate(E):
        t = e.triangle
        R = t.vertices[cw(e.side)]
        # if last leg of triangle makes left turn/straight,
        # instead of right turn previously
        # we have found the overlapping triangle
        if orient2d(P, Q, R) >= 0:  # and overlap is None:
            overlap = e
            idx = i
            break
    assert overlap is not None
    return idx
コード例 #2
0
def point_to_each_other(triangle, side):
    """Asserts that two kinetic vertices along the wavefront
    point to each other
    """
    v0, v1 = triangle.vertices[cw(side)], triangle.vertices[ccw(side)]
    assert v0.left is v1, "@triangle: {} - v0 := {}, v0.left {} != {}".format(
        id(triangle), id(v0), id(v0.left), id(v1))
    assert v1.right is v0, "@triangle: {} - v1 := {}, v1.right {} != {}".format(
        id(triangle), id(v1), id(v1.right), id(v0))
コード例 #3
0
def check_bisector_direction(triangle, side, time):
    """Asserts that orientation of bisector is straight or turning left wrt unconstrained edge on its left"""
    v0, v1 = triangle.vertices[ccw(side)], triangle.vertices[cw(side)]
    pos2 = add(v1.position_at(time), v1.velocity)
    pos0, pos1 = v0.position_at(time), v1.position_at(time)
    ori = orient2d(pos0, pos1, pos2)
    assert ori > 0 or near_zero(
        ori
    ), "v0, v1: {}, {} | orientation: {} | positions: {}; {}; {}".format(
        v0.info, v1.info, orient2d(pos0, pos1, pos2), pos0, pos1,
        pos2)  # left / straight
コード例 #4
0
ファイル: initialize.py プロジェクト: bmmeijers/grassfire
def check_ktriangles(L, now=0):
    """Check whether kinetic triangles are all linked up properly
    """
    valid = True
    # check if neighbours are properly linked
    for ktri in L:
        if ktri.stops_at is not None:
            continue
        for ngb in ktri.neighbours:
            if ngb is not None:
                if ktri not in ngb.neighbours:
                    print(("non neighbouring triangles:", id(ktri), "and",
                           id(ngb)))
                    valid = False
        for v in ktri.vertices:
            if ktri.is_finite:
                if not ((v.starts_at <= now and v.stops_at is not None
                         and v.stops_at > now) or
                        (v.starts_at <= now and v.stops_at is None)):
                    print(("triangle", id(ktri), " with invalid kinetic"
                           " vertex", id(v), " for this time"))
                    print(("", v.starts_at, v.stops_at))
                    valid = False
    # check if the sides of a triangle share the correct vertex at begin / end
    if False:  # FIXME: enable!!!
        for ktri in L:
            for i in range(3):
                ngb = ktri.neighbours[i]
                if ngb is not None:
                    j = ngb.neighbours.index(ktri)
                    if not ngb.vertices[cw(j)] is ktri.vertices[ccw(i)]:
                        print("something wrong with vertices 1")
                        valid = False
                    if not ngb.vertices[ccw(j)] is ktri.vertices[cw(i)]:
                        print("something wrong with vertices 2")
                        valid = False
    # FIXME: check orientation of triangles ????
    # -- could be little difficult with initial needle triangles at terminal
    # vertices of PSLG
    return valid
コード例 #5
0
ファイル: initialize.py プロジェクト: bmmeijers/grassfire
def split_star(v):
    """Splits the edges of the star in groups by constrained edges"""
    around = [e for e in StarEdgeIterator(v)]
    groups = []
    group = []
    for edge in around:
        t, s = edge.triangle, edge.side
        group.append(edge)
        # if the edge ahead is constrained,
        # we make a new group
        if Edge(t, ccw(s)).constrained:
            groups.append(group)
            group = []
    if group:
        groups.append(group)
    # if we have only one group, we do not have to change the first and the
    # last
    if len(groups) <= 1:
        return groups

    # merge first and last group
    # this is necessary when the group does not start at a constrained edge
    edge = groups[0][0]
    if not edge.triangle.constrained[cw(edge.side)]:
        last = groups.pop()
        last.extend(groups[0])
        groups[0] = last

    # -- post condition checks
    # the first and last triangle in the group have a constrained
    # and the rest of the triangles in the middle of every group do not
    for group in groups:
        first, last = group[0], group[-1]
        assert first.triangle.constrained[cw(first.side)]
        assert last.triangle.constrained[ccw(last.side)]
        for middle in group[1:-1]:
            assert not middle.triangle.constrained[cw(middle.side)]
            assert not middle.triangle.constrained[ccw(middle.side)]

    return groups
コード例 #6
0
def handle_parallel_edge_event_even_legs(t, e, pivot, now, step, skel, queue, immediate):

    logging.info("* parallel|even  :: tri>> #{} [{}]".format(id(t), t.info))

    logging.debug('At start of handle_parallel_edge_event with same size legs')
    logging.debug("Edge with inf fast vertex collapsing! {0}".format(t.neighbours[e] is None))

    # FIXME: pre-conditions for this handler
    # there should be 1 edge with zero length, and other 2 edges should have length?
    # can it also be that this handler deals with collapse to point ???

    # does not hold! -> 

    # triangle can also be like:
    # *-------------------------*
    #  `---------------*'''''''

    # we are collapsing the edge opposite of the inf fast pivot vertex
    # this assumes that v1 and v2 need to be on the same location!!!
    assert t.vertices.index(pivot) == e
    assert t.vertices[e] is pivot
#    assert pivot.inf_fast
    # stop the non-infinite vertices
    v1 = t.vertices[ccw(e)]
    v2 = t.vertices[cw(e)]
    sk_node, newly_made = stop_kvertices([v1,v2], step, now)
    if newly_made:
        skel.sk_nodes.append(sk_node)

    # stop the pivot as well
    if pivot.stop_node is not None:
        logging.debug("Infinite fast pivot already stopped, but should not be stopped(?)")
#    assert pivot.stop_node is None
#    assert pivot.stops_at is None
    pivot.stop_node = sk_node
    pivot.stops_at = now
    # this is not necessary, is it?
    ## update_circ(pivot, v1, now)
    ## update_circ(v2, pivot, now)

    # we "remove" the triangle itself
    t.stops_at = now

    n = t.neighbours[e]
    msg = "schedule adjacent neighbour for *IMMEDIATE* processing" if n is not None else "no neighbour to collapse simultaneously"
    logging.debug("*** neighbour n: {} ".format(msg))
    if n is not None:
        n.neighbours[n.neighbours.index(t)] = None
        if n.event is not None and n.stops_at is None:
            logging.debug(n.event)
            schedule_immediately(n, now, queue, immediate)
コード例 #7
0
ファイル: edge.py プロジェクト: bmmeijers/grassfire
def handle_edge_event_1side(evt, step, skel, queue, immediate, pause):
    """Handle a collapse of a triangle with 1 side collapsing.

    Important: The triangle collapses to a line segment.
    """
    t = evt.triangle

    logging.info("* edge 1side     :: tri>> #{} [{}]".format(id(t), t.info))

    logging.debug(evt.side)
    assert len(evt.side) == 1, len(evt.side)
    e = evt.side[0]
    logging.debug(
        "wavefront edge collapsing? {0}".format(t.neighbours[e] is None))
    now = evt.time
    v0 = t.vertices[e]
    v1 = t.vertices[ccw(e)]
    v2 = t.vertices[cw(e)]
    # stop the two vertices of this edge and make new skeleton node
    # replace 2 vertices with new kinetic vertex
    sk_node, newly_made = stop_kvertices([v1, v2], step, now)
    if newly_made:
        skel.sk_nodes.append(sk_node)
    kv = compute_new_kvertex(v1.ul, v2.ur, now, sk_node,
                             len(skel.vertices) + 1, v1.internal
                             or v2.internal, pause)
    # FIXME: should we update the left and right wavefront line refs here?
    logging.debug("Computed new kinetic vertex {} [{}]".format(
        id(kv), kv.info))
    logging.debug("v1 := {} [{}]".format(id(v1), v1.info))
    logging.debug("v2 := {} [{}]".format(id(v2), v2.info))
    logging.debug(kv.position_at(now))
    logging.debug(kv.position_at(now + 1))
    # append to skeleton structure, new kinetic vertex
    skel.vertices.append(kv)
    sk_node, newly_made = stop_kvertices([v0, kv], step, now)
    if newly_made:
        skel.sk_nodes.append(sk_node)
    # we "remove" the triangle itself
    t.stops_at = now
コード例 #8
0
ファイル: initialize.py プロジェクト: bmmeijers/grassfire
def init_skeleton(dt):
    """Initialize a data structure that can be used for making the straight
    skeleton.
    """
    skel = Skeleton()

    # make skeleton nodes
    # -> every triangulation vertex becomes a skeleton node
    nodes = {}
    avg_x = 0.0
    avg_y = 0.0
    for v in dt.vertices:
        if v.is_finite:
            nodes[v] = SkeletonNode(pos=(v.x, v.y), step=-1, info=v.info)
            avg_x += v.x / len(dt.vertices)
            avg_y += v.y / len(dt.vertices)
    centroid = InfiniteVertex()
    centroid.origin = (avg_x, avg_y)
    # make kinetic triangles, so that for every delaunay triangle we have
    # a kinetic counter part

    ktriangles = []  # all kinetic triangles
    # mapping from delaunay triangle to kinetic triangle

    internal_triangles = set()
    for _, depth, triangle in RegionatedTriangleIterator(dt):
        if depth == 1:
            # FIXME: why not put the depth here as identifier into the triangle
            # holes can then be separated from others (and possibly calculated in parallel)
            internal_triangles.add(triangle)

    triangle2ktriangle = {}
    for idx, t in enumerate(dt.triangles, start=1):
        # skip the external triangle
        # if t is dt.external:
        #    continue
        k = KineticTriangle()
        k.info = idx
        triangle2ktriangle[t] = k
        ktriangles.append(k)
        k.internal = t in internal_triangles  # whether triangle is internal to a polygon
    del idx

    link_around = []
    # set up properly the neighbours of all kinetic triangles
    # blank out the neighbour, if a side is constrained
    unwanted = []
    #it = TriangleIterator(dt)
    # skip the external triangle (which is the first the iterator gives)
    #     next(it)
    for t in dt.triangles:
        k = triangle2ktriangle[t]

        for i in range(3):
            edge = Edge(t, i)
            # k.wavefront_directions[i] = make_unit_vector(edge)
            k.wavefront_support_lines[i] = make_support_line(edge)

        for j, n in enumerate(t.neighbours):
            # set neighbour pointer to None if constrained side
            if t.constrained[j]:
                continue
            # skip linking to non-existing triangle
            if n is None or n.vertices[2] is None:
                #            if n.external:
                unwanted.append(k)
                continue
            k.neighbours[j] = triangle2ktriangle[n]

    # FIXME: duplicate <-> each KTriangle has wavefront!
    # unit_vectors = make_unit_vectors(dt)
    # make kinetic vertices
    # and link them to the kinetic triangles
    # also make sure that every kinetic vertex is related to a skeleton node
    kvertices = []
    #     ktri_no_apex = []
    #     one_ktri_between = {}
    #     with open("/tmp/bisectors.wkt", "w") as bisector_fh:
    #         print >> bisector_fh, "wkt"
    ct = 0
    for v in dt.vertices:
        assert v.is_finite, "infinite vertex found"

        groups = split_star(v)
        if len(groups) == 1:
            raise NotImplementedError(
                "not yet dealing with PSLG in initial conversion")

        for group in groups:
            first, last = group[0], group[-1]

            # compute turn type at vertex
            tail, mid1 = Edge(last.triangle, ccw(last.side)).segment
            # print(tail.x, tail.y, ";", mid1.x, mid1.y)
            mid2, head = Edge(first.triangle, cw(first.side)).segment
            # print(mid2.x, mid2.y, ";", head.x, head.y)
            assert mid1 is mid2
            turn = orient2d((tail.x, tail.y), (mid1.x, mid1.y),
                            (head.x, head.y))
            #    left : + [ = ccw ]
            #     straight : 0.
            #     right : - [ = cw ]

            if turn < 0:
                turn_type = "RIGHT - REFLEX"
            elif turn > 0:
                turn_type = "LEFT - CONVEX"
            else:
                turn_type = "STRAIGHT"
            # print(turn_type)
            # print("--")

            # FIXME: duplicate <-> each KTriangle has wavefront!
            right = triangle2ktriangle[first.triangle].wavefront_support_lines[
                cw(first.side)]
            left = triangle2ktriangle[last.triangle].wavefront_support_lines[
                ccw(last.side)]

            assert left is not None
            assert right is not None
            intersector = WaveFrontIntersector(left, right)

            bi = intersector.get_bisector()
            # print("")
            # print(v, id(v), v.info)
            # print(left)
            # print(right)
            #            print("=-=-=")
            #            print(id(first.triangle))
            #            print(first_line)
            #            print("")
            #            print(id(last.triangle))
            #            print(last_line)
            #            print("=-=-=")
            #            print("")

            #             bi = None
            #             left_translated = left.translated(left.w)
            #             right_translated = right.translated(right.w)
            #             intersect = Intersector(left_translated, right_translated)
            #             #
            #             # == 3 possible outcomes in 2D: ==
            #             #
            #             # 0. overlapping lines - always intersecting in a line
            #             # 1. crossing - point2
            #             # 2. parallel - no intersection
            #             #
            #             if intersect.intersection_type() == IntersectionResult.LINE:
            #                 bi = tuple(left.w)
            #             elif intersect.intersection_type() == IntersectionResult.POINT:
            # #                velocity
            #                 bi = make_vector(end=intersect.result, start=(v.x, v.y))
            #             elif intersect.intersection_type() == IntersectionResult.NO_INTERSECTION:
            #                 # print('no intersection, parallel wavefronts - not overlapping?')
            #                 logging.warning('no intersection, parallel wavefronts - not overlapping?')
            #                 bi = tuple(left.w)
            #             assert bi is not None

            ur = right.line  # unit_vectors[first.triangle][cw(first.side)]
            ul = left.line  # unit_vectors[last.triangle][ccw(last.side)]
            #            bi = bisector(ul, ur)

            #             print v, ul, ur, bi
            #             for edge in group:
            #                 print "", edge.triangle
            ct += 1
            kv = KineticVertex()
            kv.turn = turn_type
            kv.info = ct  ### "{}-{}".format(ct, turn)
            kv.origin = (v.x, v.y)
            kv.velocity = bi
            kv.start_node = nodes[v]
            kv.starts_at = 0
            #            kv.ul = first_line # ul # FIXME: point to original Line2 ?
            #            kv.ur = last_line # ur

            kv.ul = ul
            kv.ur = ur

            kv.wfl = left
            kv.wfr = right

            # print("    {}".format(kv.origin) )
            # print("    {}".format(kv.wfl) )
            # print("    {}".format(kv.wfr) )

            for edge in group:
                ktriangle = triangle2ktriangle[edge.triangle]
                ktriangle.vertices[edge.side] = kv
                kv.internal = ktriangle.internal
            kvertices.append(kv)
            # link vertices to each other in circular list
            link_around.append(
                ((last.triangle, cw(last.side)), kv, (first.triangle,
                                                      ccw(first.side))))

# #             print ""
#         it = StarEdgeIterator(v)
#         around = [e for e in it]
# #             with open("/tmp/vertexit.wkt", "w") as fh:
# #                 output_triangles([e.triangle for e in around], fh)
#
#         constraints = []
#         for i, e in enumerate(around):
#             if e.triangle.constrained[cw(e.side)]:
#                 constraints.append(i)
#
#         # FIXME:
#         # Check here how many constrained edges we have outgoing of
#         # this vertex.
#         #
#         # In case 0: degenerate case, should not happen
#         # In case 1: we should make two kvertices vertices
#         #
#         # We do not handle this properly at this moment.
#         #
#         # In case 2 or more the following is fine.
#         if len(constraints) == 0:
#             raise ValueError("Singular point found")
#         else:
#             # rotate the list of around triangles,
#             # so that we start with a triangle that has a constraint
#             # side
#             if constraints[0] != 0:
#                 shift = -constraints[0]  # how much to rotate
#                 d = deque(around)
#                 d.rotate(shift)
#                 around = list(d)
#                 # also update which triangles have a constraint edge
#                 constraints = [idx + shift for idx in constraints]
#
#             # make two bisectors at a terminal vertex
#             if len(constraints) == 1:
#
#                 assert constraints[0] == 0
#                 edge = around[0]
#                 start, end = v, edge.triangle.vertices[ccw(edge.side)]
#                 vec = normalize((end.x - start.x, end.y - start.y))
#                 # FIXME: Replace perp with rotate90cw / rotate90ccw
#
#                 # from segment over terminal vertex to this kinetic vertex,
#                 # turns right
#                 # (first bisector when going ccw at end)
#                 p2 = tuple(map(add, start, rotate90ccw(vec)))
#                 p1 = v
#                 p0 = tuple(map(add, start, rotate90ccw(rotate90ccw(vec))))
#                 bi = bisector(p0, p1, p2)
# #                 print >> bisector_fh,
# ##"LINESTRING({0[0]} {0[1]}, {1[0]} {1[1]})".format(
# ##p1, map(add, p1, bi))
# #                     print nodes[v]
#
#                 kvA = KineticVertex()
#                 kvA.origin = (p1.x, p1.y)
#                 kvA.velocity = bi
#                 kvA.start_node = nodes[v]
#                 kvA.starts_at = 0
#                 kvertices.append(kvA)
#
#                 # from segment to this vertex, turns left
#                 # second bisector when going ccw at end
#                 p2 = tuple(map(add, start, rotate90ccw(rotate90ccw(vec))))
#                 p1 = v
#                 p0 = tuple(
#                     map(add, start, rotate90ccw(rotate90ccw(rotate90ccw(
#                 bi = bisector(p0, p1, p2)
# #                 print >> bisector_fh,
# "LINESTRING({0[0]} {0[1]}, {1[0]} {1[1]})".format(p1, map(add, p1, bi))
#                 # FIXME insert additional triangle at this side
# #                     print nodes[v]
#
#                 kvB = KineticVertex()
#                 kvB.origin = (p1.x, p1.y)
#                 kvB.velocity = bi
#                 kvB.start_node = nodes[v]
#                 kvB.starts_at = 0
#                 kvertices.append(kvB)
#
#                 groups = [around]
#
#                 split_idx = find_overlapping_triangle(around)
#
# #                     print split_idx
# #                     print len(around)
#                 # determine which triangles get an incidence with the
#                 # first and which with the second kvertices vertex
#
#                 # first go with kvA
#                 # second go with kvB
#                 first, second = around[:split_idx], around[split_idx + 1:]
# #                     print "first ", first
# #                     print "second", second
#                 mid = around[split_idx]
# #                     print "mid   ", mid
#
#                 # go with kvA
#                 for e in first:
#                     ktriangle = triangle2ktriangle[e.triangle]
#                     ktriangle.vertices[e.side] = kvA
#                 # go with kvB
#                 for e in second:
#                     ktriangle = triangle2ktriangle[e.triangle]
#                     ktriangle.vertices[e.side] = kvB
#
#                 # for the mid triangle it depends where it should go
#                 # based on adding an additional kvertices triangle into
#                 # the triangulation here...
#
#                 # FIXME: the placement of points A and B should be
#                 # dependent on the distance between A and L or A and F
#                 # to not get False negatives out of the is_quad
#                 # classification
#                 triangle = mid.triangle
# #                     print "PIVOT POINT INDEX", mid.side
#                 first_leg = ccw(mid.side)
#                 last_leg = cw(mid.side)
#                 L = triangle.vertices[last_leg]
#                 F = triangle.vertices[first_leg]
#
#                 A = map(add, kvA.origin, kvA.velocity)
#                 B = map(add, kvB.origin, kvB.velocity)
#                 O = triangle.vertices[mid.side]
# # print "first", first_leg,"|" , F, "(cw)", "last", last_leg, "|" ,L,
# # "(ccw) around", O
#
#                 first_quad = [O, A, F, B, O]
#                 last_quad = [O, A, L, B, O]
#                 first_ok = is_quad(first_quad)
#                 last_ok = is_quad(last_quad)
#
#                 # if first is True and second False
#                 # assign ktriangles triangle to kvA/kvB and the corner to kvB
#
#                 # if both not ok, probably at convex hull overlapping with
#                 # infinite triangle
#                 # only, so take guess and use the first leg
#                 if first_ok or (not first_ok and not last_ok):
#                     ktriangle = triangle2ktriangle[mid.triangle]
#                     ktriangle.vertices[mid.side] = kvB
#
#                     knew = KineticTriangle()
#                     knew.vertices[0] = kvB
#                     knew.vertices[1] = kvA
#                     knew.vertices[2] = None
#
#                     X, Y = mid.triangle, mid.triangle.neighbours[
#                         ccw(first_leg)]
#                     sideX = X.neighbours.index(Y)
#                     sideY = Y.neighbours.index(X)
#
#                     key = tuple(sorted([X, Y]))
#                     if key not in one_ktri_between:
#                         one_ktri_between[key] = []
#                     one_ktri_between[key].append(
#                         (knew,
#                          triangle2ktriangle[Y],
#                             sideY,
#                             triangle2ktriangle[X],
#                             sideX))
#
#                 # if first is false and second True
#                 # assign ktriangles triangle to kvA/kvB and the corner to kvA
#                 elif last_ok:
#                     ktriangle = triangle2ktriangle[mid.triangle]
#                     ktriangle.vertices[mid.side] = kvA
#
#                     knew = KineticTriangle()
#                     knew.vertices[0] = kvB
#                     knew.vertices[1] = kvA
#                     knew.vertices[2] = None
# #                         ktri_no_apex.append(knew)
#
#                     X = mid.triangle
#                     Y = mid.triangle.neighbours[cw(last_leg)]
#                     sideX = X.neighbours.index(Y)
#                     sideY = Y.neighbours.index(X)
#
#                     key = tuple(sorted([X, Y]))
#                     if key not in one_ktri_between:
#                         one_ktri_between[key] = []
#                     one_ktri_between[key].append(
#                         (knew,
#                          triangle2ktriangle[X],
#                             sideX,
#                             triangle2ktriangle[Y],
#                             sideY))
#
#                 # add 2 entries to link_around list
#                 # one for kvA and one for kvB
#                 # link kvA and kvB to point to each other directly
#                 kvA.left = kvB, 0
#                 link_around.append(
#                     (None, kvA, (first[0].triangle, ccw(first[0].side))))
#                 kvB.right = kvA, 0
#                 link_around.append(
#                     ((second[-1].triangle, cw(second[-1].side)), kvB, None))
#
#             # make bisectors
#             else:
#
#                 assert len(constraints) >= 2
#                 # group the triangles around the vertex
#                 constraints.append(len(around))
#                 groups = []
#                 for lo, hi in zip(constraints[:-1], constraints[1:]):
#                     groups.append(around[lo:hi])
#
#                 # per group make a bisector and KineticVertex
#                 for group in groups:
#                     begin, end = group[0], group[-1]
#                     p2 = begin.triangle.vertices[
#                         ccw(begin.side)]  # the cw vertex
#                     p1 = v
#                     # the ccw vertex
#                     p0 = end.triangle.vertices[cw(end.side)]
#                     bi = bisector(p0, p1, p2)
# #                     print >> bisector_fh,
# "LINESTRING({0[0]} {0[1]}, {1[0]} {1[1]})".format(p1, map(add, p1, bi))
#                     kv = KineticVertex()
#                     kv.origin = (p1.x, p1.y)
#                     kv.velocity = bi
#                     kv.start_node = nodes[v]
#                     kv.starts_at = 0
#                     for edge in group:
#                         ktriangle = triangle2ktriangle[edge.triangle]
#                         ktriangle.vertices[edge.side] = kv
#                     kvertices.append(kv)
#                     # link vertices to each other in circular list
#                     link_around.append(((end.triangle, cw(end.side)),
# kv, (begin.triangle, ccw(begin.side))))

# link vertices in circular list
    for left, kv, right in link_around:  # left is cw, right is ccw
        assert left is not None
        assert right is not None
        # if left is not None:
        # left
        cwv = triangle2ktriangle[left[0]].vertices[left[1]]
        kv.left = cwv, 0

        # if right is not None:
        ccwv = triangle2ktriangle[right[0]].vertices[right[1]]
        kv.right = ccwv, 0

    for left, kv, right in link_around:  # left is cw, right is ccw
        assert kv.left.wfr is kv.wfl, "{} vs\n {}".format(kv.left.wfr, kv.wfl)
        assert kv.wfr is kv.right.wfl
        assert kv.is_stopped == False

    # -- copy infinite vertices into the kinetic triangles
    # make dico of infinite vertices (lookup by coordinate value)
    infinites = {}
    for t in triangle2ktriangle:
        for i, v in enumerate(t.vertices):
            #             print(t, t.vertices)
            if v is not None and not v.is_finite:
                infv = InfiniteVertex()
                infv.origin = (v[0], v[1])
                infinites[(v[0], v[1])] = infv
    assert len(infinites) == 3
    # link infinite triangles to the infinite vertex
    for (t, kt) in triangle2ktriangle.items():
        for i, v in enumerate(t.vertices):
            if v is not None and not v.is_finite:
                kt.vertices[i] = infinites[(v[0], v[1])]


#     # deal with added kinetic triangles at terminal vertices
#     for val in one_ktri_between.itervalues():
#         if len(val) == 1:
#             knew, x, side_x, y, side_y, = val[0]
#             knew.neighbours[0] = x
#             knew.neighbours[1] = y
#             knew.neighbours[2] = None
#             x.neighbours[side_x] = knew
#             y.neighbours[side_y] = knew
#             knew.vertices[2] = x.vertices[ccw(side_x)]
#             ktriangles.append(knew)
#         elif len(val) == 2:
#             for i, v in enumerate(val):
#                 # the other triangle between these 2 terminal vertices
#                 # is the first value of the other tuple
#                 kother = val[(i + 1) % 2][0]
#                 knew, x, side_x, y, side_y, = v
#                 # link to each other and to neighbour x
#                 knew.neighbours[0] = x
#                 knew.neighbours[1] = kother
#                 knew.neighbours[2] = None
#                 x.neighbours[side_x] = knew
#                 y.neighbours[side_y] = kother
#                 # link to vertex
#                 knew.vertices[2] = x.vertices[ccw(side_x)]
#                 ktriangles.append(knew)
#         else:
#             raise ValueError(
#                 "Unexpected # kinetic triangles at terminal vertex")

# there are 3 infinite triangles that are supposed to be removed
# these triangles were already stored in the unwanted list
    remove = []
    for kt in ktriangles:
        if [isinstance(v, InfiniteVertex)
                for v in kt.vertices].count(True) == 2:
            remove.append(kt)
    assert len(remove) == 3
    assert len(unwanted) == 3
    assert remove == unwanted
    # remove the 3 unwanted triangles and link their neighbours together
    link = []
    for kt in unwanted:
        v = kt.vertices[kt.neighbours.index(None)]
        assert isinstance(v, KineticVertex)
        neighbour_cw = rotate_until_not_in_candidates(kt, v, cw, unwanted)
        neighbour_ccw = rotate_until_not_in_candidates(kt, v, ccw, unwanted)
        side_cw = ccw(neighbour_cw.vertices.index(v))
        side_ccw = cw(neighbour_ccw.vertices.index(v))
        link.append((neighbour_cw, side_cw, neighbour_ccw))
        link.append((neighbour_ccw, side_ccw, neighbour_cw))
    for item in link:
        ngb, side, new_ngb, = item
        ngb.neighbours[side] = new_ngb
    for kt in unwanted:
        kt.vertices = [None, None, None]
        kt.neighbours = [None, None, None]
        ktriangles.remove(kt)
    # replace the infinite vertices by one point in the center of the PSLG
    # (this could be the origin (0,0) if we would scale input to [-1,1] range
    for kt in ktriangles:
        for i, v in enumerate(kt.vertices):
            if isinstance(v, InfiniteVertex):
                kt.vertices[i] = centroid
    assert check_ktriangles(ktriangles)
    ktriangles.sort(
        key=lambda t: (t.vertices[0].origin[1], t.vertices[0].origin[0]))
    skel.sk_nodes = list(nodes.values())
    skel.triangles = ktriangles
    skel.vertices = kvertices
    # INITIALIZATION FINISHES HERE

    # with open('/tmp/lines.wkt', 'w') as fh:
    #     xaxis = Line2.from_points( (0, 0), (1, 0) )
    #     yaxis = Line2.from_points( (0, 0), (0, 1) )

    #     fh.write("wkt")
    #     fh.write("\n")

    #     fh.write(as_wkt(*xaxis.visualize()))
    #     fh.write("\n")

    #     fh.write(as_wkt(*yaxis.visualize()))
    #     fh.write("\n")

    #     for kt in skel.triangles:
    #         for line in filter(lambda x: x is not None, kt.wavefront_support_lines):
    #             fh.write(as_wkt(*line.visualize()))
    #             fh.write("\n")

    return skel
コード例 #9
0
ファイル: edge.py プロジェクト: bmmeijers/grassfire
def handle_edge_event(evt, step, skel, queue, immediate, pause):
    """Handles triangle collapse, where exactly 1 edge collapses"""
    t = evt.triangle
    logging.info("* edge           :: tri>> #{} [{}]".format(id(t), t.info))

    logging.debug(evt.side)
    assert len(evt.side) == 1, len(evt.side)
    # take edge e
    e = evt.side[0]
    logging.debug(
        "wavefront edge collapsing? {0}".format(t.neighbours[e] is None))
    is_wavefront_collapse = t.neighbours[e] is None
    #    if t.neighbours.count(None) == 2:
    #        assert t.neighbours[e] is None
    now = evt.time
    v1 = t.vertices[ccw(e)]
    v2 = t.vertices[cw(e)]

    # v1.
    logging.debug("v1 := {} [{}] -- stop_node: {}".format(
        id(v1), v1.info, v1.stop_node))
    logging.debug("v2 := {} [{}] -- stop_node: {}".format(
        id(v2), v2.info, v2.stop_node))

    # FIXME: assertion is not ok when this is triangle from spoke collapse?
    if is_wavefront_collapse and not v1.is_stopped and not v2.is_stopped:
        assert v1.right is v2
        assert v2.left is v1

    # stop the two vertices of this edge and make new skeleton node
    # replace 2 vertices with new kinetic vertex

    # +--- new use of wavefronts ------------------------------ #
    # ⋮
    a = v1.wfl
    b = v1.wfr
    if is_wavefront_collapse and not v1.is_stopped and not v2.is_stopped:
        assert v2.wfl is b
    c = v2.wfr
    #
    intersector = WaveFrontIntersector(a, c)
    bi = intersector.get_bisector()
    logging.debug(bi)
    # in general position the new position of the node can be constructed by intersecting 3 pairs of wavefronts
    # (a,c), (a,b), (b,c)
    # in case (a,c) are parallel, this is new infinitely fast vertex and triangles are locked between
    # or (a,c) are parallel due to spoke collapse (then new vertex is on straight line, splitting it in 2 times 90 degree angles)
    # in case (a,b) are parallel, then v1 is straight -- no turn
    # in case (b,c) are parallel, then v2 is straight -- no turn
    pos_at_now = None
    try:
        intersector = WaveFrontIntersector(a, c)
        pos_at_now = intersector.get_intersection_at_t(now)
        logging.debug("POINT({0[0]} {0[1]});a;c".format(pos_at_now))
    except ValueError:
        pass
    # iff the wavefronts wfl/wfr are parallel
    # then only the following 2 pairs of wavefronts can be properly intersected!
    # try:
    #     intersector = WaveFrontIntersector(a, b)
    #     pos_at_now = intersector.get_intersection_at_t(now)
    #     logging.debug("POINT({0[0]} {0[1]});a;b".format(pos_at_now))
    # except ValueError:
    #     pass
    # #
    # try:
    #     intersector = WaveFrontIntersector(b, c)
    #     pos_at_now = intersector.get_intersection_at_t(now)
    #     logging.debug("POINT({0[0]} {0[1]});b;c".format(pos_at_now))            #
    # except ValueError:
    #     pass
    # ⋮
    # +--- new use of wavefronts ------------------------------ #

    sk_node, newly_made = stop_kvertices([v1, v2], step, now, pos=pos_at_now)
    if newly_made:
        skel.sk_nodes.append(sk_node)
    kv = compute_new_kvertex(v1.ul, v2.ur, now, sk_node,
                             len(skel.vertices) + 1, v1.internal
                             or v2.internal, pause)

    # ---- new use of wavefronts ---------- #
    kv.wfl = v1.wfl  #
    kv.wfr = v2.wfr  #
    # ---- new use of wavefronts ---------- #

    logging.debug("Computed new kinetic vertex {} [{}]".format(
        id(kv), kv.info))
    logging.debug("v1 := {} [{}]".format(id(v1), v1.info))
    logging.debug("v2 := {} [{}]".format(id(v2), v2.info))
    # logging.debug(kv.position_at(now))
    # logging.debug(kv.position_at(now+1))
    # logging.debug("||| {} | {} | {} ||| ".format( v1.left.position_at(now), sk_node.pos, v2.right.position_at(now) ))
    # logging.debug("||| {} ||| ".format(signed_turn( v1.left.position_at(now), sk_node.pos, v2.right.position_at(now) )))
    # logging.debug("||| {} ||| ".format(get_bisector( v1.left.position_at(now), sk_node.pos, v2.right.position_at(now) )))

    if v1.left:
        logging.debug(v1.left.position_at(now))
    else:
        logging.warning("no v1.left")
    if v2.right:
        logging.debug(v2.right.position_at(now))
    else:
        logging.warning("no v2.right")
    if kv.inf_fast:
        logging.debug("New kinetic vertex moves infinitely fast!")
    # append to skeleton structure, new kinetic vertex
    skel.vertices.append(kv)
    # update circular list of kinetic vertices
    update_circ(v1.left, kv, now)
    update_circ(kv, v2.right, now)

    # def sign(val):
    #     if val > 0:
    #         return +1
    #     elif val < 0:
    #         return -1
    #     else:
    #         return 0

    # bisector_check = get_bisector( v1.left.position_at(now), sk_node.pos, v2.right.position_at(now) )
    # if not kv.inf_fast:
    #     logging.debug("{} [{}]".format(v1.left, v1.left.info))
    #     logging.debug("{} [{}]".format(v2.right, v2.right.info))
    #     logging.debug("{0} vs {1}".format(bisector_check, kv.velocity))
    #     if sign(bisector_check[0]) == sign(kv.velocity[0]) and sign(bisector_check[1]) == sign(kv.velocity[1]):
    #         logging.debug('signs agree')
    #     else:
    #         logging.warning("""

    #         BISECTOR SIGNS DISAGREE

    #         """)

    #         kv.velocity = (sign(bisector_check[0]) * abs(kv.velocity[0]), sign(bisector_check[1]) * abs(kv.velocity[1]))
    #         raise ValueError('bisector signs disagree')

    # ---- new use of wavefronts ---------- #
    # post condition
    assert kv.wfl is kv.left.wfr
    assert kv.wfr is kv.right.wfl
    # ---- new use of wavefronts ---------- #

    # get neighbours around collapsing triangle
    a = t.neighbours[ccw(e)]
    b = t.neighbours[cw(e)]
    n = t.neighbours[e]
    # second check: is vertex infinitely fast?

    #    is_inf_fast_a = is_infinitely_fast(get_fan(a, v2, cw), now)
    #    is_inf_fast_b = is_infinitely_fast(get_fan(b, v1, ccw), now)
    #    if is_inf_fast_a and is_inf_fast_b:
    #        if not kv.inf_fast:
    #            logging.debug("New kinetic vertex: ***Upgrading*** to infinitely fast moving vertex!")
    #            kv.inf_fast = True
    #

    fan_a = []
    fan_b = []
    if a is not None:
        logging.debug("replacing vertex for neighbours at side A")
        a_idx = a.neighbours.index(t)
        a.neighbours[a_idx] = b
        fan_a = replace_kvertex(a, v2, kv, now, cw, queue, immediate)

        if fan_a:
            e = Edge(fan_a[-1], cw(fan_a[-1].vertices.index(kv)))
            orig, dest = e.segment
            import math
            if (near_zero(math.sqrt(orig.distance2_at(dest, now)))):
                logging.info(
                    "collapsing neighbouring edge, as it is very tiny -- cw")
                schedule_immediately(fan_a[-1], now, queue, immediate)
    if b is not None:
        logging.debug("replacing vertex for neighbours at side B")
        b_idx = b.neighbours.index(t)
        b.neighbours[b_idx] = a
        fan_b = replace_kvertex(b, v1, kv, now, ccw, queue, immediate)
        if fan_b:
            e = Edge(fan_b[-1], ccw(fan_b[-1].vertices.index(kv)))
            orig, dest = e.segment
            import math
            if (near_zero(math.sqrt(orig.distance2_at(dest, now)))):
                logging.info(
                    "collapsing neighbouring edge, as it is very tiny -- ccw")
                schedule_immediately(fan_b[-1], now, queue, immediate)

    if n is not None:
        logging.debug(
            "*** neighbour n: schedule adjacent neighbour for *IMMEDIATE* processing"
        )
        n.neighbours[n.neighbours.index(t)] = None
        if n.event is not None and n.stops_at is None:
            schedule_immediately(n, now, queue, immediate)


#    if t.info == 134:
#        raise NotImplementedError('problem: #134 exists now')

# we "remove" the triangle itself
    t.stops_at = now

    # process parallel fan
    if kv.inf_fast:
        if fan_a and fan_b:
            # combine both fans into 1
            fan_a = list(fan_a)
            fan_a.reverse()
            fan_a.extend(fan_b)
            handle_parallel_fan(fan_a, kv, now, ccw, step, skel, queue,
                                immediate, pause)
            return
        elif fan_a:
            handle_parallel_fan(fan_a, kv, now, cw, step, skel, queue,
                                immediate, pause)
            return
        elif fan_b:
            handle_parallel_fan(fan_b, kv, now, ccw, step, skel, queue,
                                immediate, pause)
            return
コード例 #10
0
def handle_parallel_fan(fan, pivot, now, direction, step, skel, queue, immediate, pause):
    """Dispatches to correct function for handling parallel wavefronts

    fan: list of triangles, sorted (in *direction* order)
    pivot: the vertex that is going infinitely fast
    now: time at which parallel event happens
    direction: how the fan turns

    skel: skeleton structure
    queue: event queue
    immediate: queue of events that should be dealt with when finished handling
    the current event

    pause: whether we should stop for interactivity
    """
    if not fan:
        raise ValueError("we should receive a fan of triangles to handle them")
        return

    logging.debug("""
# ---------------------------------------------------------
# -- PARALLEL event handler invoked                      --
# ---------------------------------------------------------
""")
    if pause:
        logging.debug(" -- {}".format(len(fan)))
        logging.debug("    triangles in the fan: {}".format([(id(_), _.info) for _ in fan]))
        logging.debug("    fan turns: {}".format(direction))
        logging.debug("    pivot: {} [{}]".format(id(pivot), pivot.info))
        interactive_visualize(queue, skel, step, now)

    assert pivot.inf_fast
    first_tri = fan[0]
    last_tri = fan[-1]
    # special case, infinite fast vertex in *at least* 1 corner
    # no neighbours (3 wavefront edges)
    # -> let's collapse the edge opposite of the pivot
    if first_tri.neighbours.count(None) == 3:
        assert first_tri is last_tri #FIXME: is this true?
        dists = []
        for side in range(3):
            edge = Edge(first_tri, side)
            d = dist(*map(lambda x: x.position_at(now), edge.segment))
            dists.append(d)
        dists_sub_min = [near_zero(_ - min(dists)) for _ in dists]
        if near_zero(min(dists)) and dists_sub_min.count(True) == 1:
            logging.debug(dists_sub_min)
            logging.debug("Smallest edge collapses? {}".format(near_zero(min(dists))))
            assert dists_sub_min.count(True) == 1
    #        assert dists_sub_min.index(True) == first_tri.vertices.index(pivot)
            side = dists_sub_min.index(True)
            pivot = first_tri.vertices[dists_sub_min.index(True)]
            handle_parallel_edge_event_even_legs(first_tri, first_tri.vertices.index(pivot), pivot, now, step, skel, queue, immediate)
            return
        else:
            handle_parallel_edge_event_3tri(first_tri, first_tri.vertices.index(pivot), pivot, now, step, skel, queue, immediate)
            return

    if first_tri is last_tri:
        assert len(fan) == 1
    if direction is cw:
        left = fan[0]
        right = fan[-1]
    else:
        assert direction is ccw
        left = fan[-1]
        right = fan[0]

    left_leg_idx = ccw(left.vertices.index(pivot))
    left_leg = Edge(left, left_leg_idx)
    if left.neighbours[left_leg_idx] is not None:
        logging.debug("inf-fast pivot, but not over wavefront edge? -- left side")
    left_dist = dist(*map(lambda x: x.position_at(now), left_leg.segment))
    right_leg_idx = cw(right.vertices.index(pivot))
    right_leg = Edge(right, right_leg_idx)
    if right.neighbours[right_leg_idx] is not None:
        logging.debug("inf-fast pivot, but not over wavefront edge? -- right side")
    right_dist = dist(*map(lambda x: x.position_at(now), right_leg.segment))
    dists = [left_dist, right_dist]
    logging.debug('  distances: {}'.format(dists))
    dists_sub_min = [near_zero(_ - min(dists)) for _ in dists]
    logging.debug(dists_sub_min)
    unique_dists = dists_sub_min.count(True)
    if unique_dists == 2:
        logging.debug("Equal sized legs")
        if len(fan) == 1:
            logging.debug("Calling handle_parallel_edge_event_even_legs for 1 triangle")
            handle_parallel_edge_event_even_legs(first_tri, first_tri.vertices.index(pivot), pivot, now, step, skel, queue, immediate)
        elif len(fan) == 2:

            # FIXME: even if the 2 wavefronts collapse and the sides are equal in size
            # some triangles can be partly inside the fan and partly outside
            # e.g. if quad collapses and 2 sides collapse onto each other, the spokes
            # can still stick out of the fan ->
            # result: triangles around these spokes should be glued together (be each others neighbours)
            #    =======================+
            #   +                       \\\\\\\\\\\\\\\\\
            # inf-----------------------------------------+
            #   +                       /////////////////
            #    =======================+

#            handle_parallel_edge_event_even_legs(first_tri, first_tri.vertices.index(pivot), pivot, now, skel, queue, immediate)
#            handle_parallel_edge_event_even_legs(last_tri, last_tri.vertices.index(pivot), pivot, now, skel, queue, immediate)
            logging.debug("Calling handle_parallel_edge_event_even_legs for *multiple* triangles")
            # raise NotImplementedError('multiple triangles #{} in parallel fan that should be stopped'.format(len(fan)))

            all_2 = True
            for t in fan:
                # FIXME: left = cw / right = ccw seen from the vertex
                left_leg_idx = ccw(t.vertices.index(pivot))
                left_leg = Edge(t, left_leg_idx)
                left_dist = dist(*map(lambda x: x.position_at(now), left_leg.segment))

                right_leg_idx = cw(t.vertices.index(pivot))
                right_leg = Edge(t, right_leg_idx)
                right_dist = dist(*map(lambda x: x.position_at(now), right_leg.segment))

                dists = [left_dist, right_dist]

                dists_sub_min = [near_zero(_ - min(dists)) for _ in dists]
                logging.debug("  {}".format([left_dist, right_dist]))
                logging.debug("  {}".format(dists_sub_min))
                unique_dists = dists_sub_min.count(True)
                if unique_dists != 2:
                    all_2 = False

            # assert unique_dists == 2
            if all_2 == True:
                for t in fan:
                    handle_parallel_edge_event_even_legs(t, t.vertices.index(pivot), pivot, now, step, skel, queue, immediate)
            else:
                # not all edges in the fan have equal length, so first flip the triangles
                # before continue handling them

                # unpack the 2 triangles from the fan and flip them
                t0 = fan[0]
                t1 = fan[1]
                side0 = t0.neighbours.index(t1)
                side1 = t1.neighbours.index(t0)
                flip(t0, side0, t1, side1)

                # now if a triangle has inf-fast vertex, handle the wavefront collapse
                t0_has_inf_fast = [v.inf_fast for v in t0.vertices]
                t1_has_inf_fast = [v.inf_fast for v in t1.vertices]
                logging.debug(t0_has_inf_fast)
                logging.debug(t1_has_inf_fast)

                if True in t0_has_inf_fast:
                    logging.debug("-- Handling t0 after flip event in parallel fan --")
                    handle_parallel_edge_event_even_legs(t0, t0.vertices.index(pivot), pivot, now, step, skel, queue, immediate)

                if True in t1_has_inf_fast:
                    logging.debug("-- Handling t1 after flip event in parallel fan --")
                    handle_parallel_edge_event_even_legs(t1, t1.vertices.index(pivot), pivot, now, step, skel, queue, immediate)

            if pause:
                interactive_visualize(queue, skel, step, now)
            # raise NotImplementedError('multiple triangles in parallel fan that should be *flipped*')

        else:
            raise NotImplementedError('More than 2 triangles in parallel fan with 2 equal sized legs on the outside -- does this exist?')

        # post condition: all triangles in the fan are stopped
        # when we have 2 equal sized legs -- does not hold for Koch-rec-level-3 ???
        if len(fan) == 1 or (len(fan) == 2 and all_2 == True):
            for t in fan:
                assert t.stops_at is not None
    else:
        # check what is the shortest of the two distances
        shortest_idx = dists_sub_min.index(True)
        if shortest_idx == 1: # right is shortest, left is longest
            logging.debug("CW / left wavefront at pivot, ending at v2, is longest")
            handle_parallel_edge_event_shorter_leg(right_leg.triangle, right_leg.side, pivot, now, step, skel, queue, immediate, pause)
        elif shortest_idx == 0: # left is shortest, right is longest
            logging.debug("CCW / right wavefront at pivot, ending at v1, is longest")
            handle_parallel_edge_event_shorter_leg(left_leg.triangle, left_leg.side, pivot, now, step, skel, queue, immediate, pause)
コード例 #11
0
def handle_parallel_edge_event_3tri(t, e, pivot, now, step, skel, queue, immediate):
    logging.info("* parallel|even#3 :: tri>> #{} [{}]".format(id(t), t.info))

    logging.debug('At start of handle_parallel_edge_event for 3 triangle')
    logging.debug("Edge with inf fast vertex collapsing! {0}".format(t.neighbours[e] is None))

    # triangle is like:
    # *-------------------------*
    #  `---------------*'''''''

    # we are collapsing the edge opposite of the inf fast pivot vertex
    # this assumes that v1 and v2 need to be on the same location!!!
    assert t.vertices.index(pivot) == e
    assert t.vertices[e] is pivot
#    assert pivot.inf_fast

    left_leg_idx = ccw(t.vertices.index(pivot))
    left_leg = Edge(t, left_leg_idx)
    left_dist = dist(*map(lambda x: x.position_at(now), left_leg.segment))
    v1 = t.vertices[ccw(e)]

    right_leg_idx = cw(t.vertices.index(pivot))
    right_leg = Edge(t, right_leg_idx)
    right_dist = dist(*map(lambda x: x.position_at(now), right_leg.segment))
    v2 = t.vertices[cw(e)]

    assert v1 is not pivot
    assert v2 is not pivot

    assert pivot in t.vertices
    assert v1 in t.vertices
    assert v2 in t.vertices

    from grassfire.vectorops import dot, norm
    logging.debug(v1.velocity)
    logging.debug(v2.velocity)
    magn_v1 = norm(v1.velocity)
    magn_v2 = norm(v2.velocity)

    logging.debug('  velocity magnitude: {}'.format([magn_v1, magn_v2]))

    dists = [left_dist, right_dist]
    logging.debug('  distances: {}'.format(dists))
    dists_sub_min = [near_zero(_ - min(dists)) for _ in dists]
    logging.debug(dists_sub_min)

    # stop the non-infinite vertices at the same location
    # use the slowest moving vertex to determine the location
    if magn_v2 < magn_v1:
        sk_node, newly_made = stop_kvertices([v2], step, now)
        if newly_made:
            skel.sk_nodes.append(sk_node)
        v1.stop_node = sk_node
        v1.stops_at = now
    else:
        sk_node, newly_made = stop_kvertices([v1], step, now)
        if newly_made:
            skel.sk_nodes.append(sk_node)
        v2.stop_node = sk_node
        v2.stops_at = now

    


    # FIXME:
    # make edge between v1 and v2

#    assert pivot.stop_node is None
#    assert pivot.stops_at is None

    #FIXME: wrong sk_node for pivot
    pivot.stop_node = sk_node
    pivot.stops_at = now
    # this is not necessary, is it?
    ## update_circ(pivot, v1, now)
    ## update_circ(v2, pivot, now)

    # we "remove" the triangle itself
    t.stops_at = now


    for kv in t.vertices:
        assert kv.stops_at is not None
コード例 #12
0
def handle_parallel_edge_event_shorter_leg(t, e, pivot, now, step, skel, queue, immediate, pause):
    """Handles triangle collapse, where exactly 1 edge collapses
    
    One of the vertices of the triangle moves *infinitely* fast.

    There are 2 cases handled in this function
    
    a. triangle with long left leg, short right leg
    b. triangle with long right leg, short left leg

    Arguments:
    t -- triangle that collapses
    e -- short side over which pivot moves inf fast

    """

    logging.info("* parallel|short :: tri>> #{} [{}]".format(id(t), t.info))
    logging.debug('At start of handle_parallel_edge_event_shorter_leg')

    logging.debug("Edge with inf fast vertex collapsing! {0}".format(t.neighbours[e] is None))
    assert pivot.inf_fast
    # vertices, that are not inf fast, need to stop
    # FIXME: this is not necessarily correct ... 
    #  where they need to stop depends on the configuration
    #  -- now they are *always* snapped to same location

    v1 = t.vertices[ccw(e)]
    v2 = t.vertices[cw(e)]
    v3 = t.vertices[e]
    logging.debug("* tri>> #{} [{}]".format(id(t), t.info))
    logging.debug("* pivot #{} [{}]".format(id(pivot), pivot.info))
    logging.debug("* v1 #{} [{}]".format(id(v1), v1.info))
    logging.debug("* v2 #{} [{}]".format(id(v2), v2.info))
    logging.debug("* v3 #{} [{}]".format(id(v3), v3.info))
    assert pivot is v1 or pivot is v2

    to_stop = []
    for v in [v1, v2]:
        if not v.inf_fast:
            to_stop.append(v)

    # stop the non-infinite vertices
    sk_node, newly_made = stop_kvertices(to_stop, step, now)
    if newly_made:
        skel.sk_nodes.append(sk_node)
    if pivot.stop_node is None:
        assert pivot.stop_node is None
        assert pivot.stops_at is None
        pivot.stop_node = sk_node
        pivot.stops_at = now
        # we will update the circular list
        # at the pivot a little bit later
    else:
        logging.debug("Infinite fast pivot already stopped,"
                     " but should not be stopped(?)")
    # we "remove" the triangle itself
    t.stops_at = now
    # check that the edge that collapses is not opposite of the pivot
    # i.e. the edge is one of the two adjacent legs at the pivot
    assert t.vertices.index(pivot) != e
    kv = compute_new_kvertex(v1.ul, v2.ur, now, sk_node, len(skel.vertices) + 1, v1.internal or v2.internal, pause)
    # FIXME new wavefront -- update refs
    kv.wfl = v1.left.wfr
    kv.wfr = v2.right.wfl

    logging.debug("Computed new kinetic vertex {} [{}]".format(id(kv), kv.info))
    if kv.inf_fast:
        logging.debug("New kinetic vertex moves infinitely fast!")
    # get neighbours around collapsing triangle
    a = t.neighbours[ccw(e)]
    b = t.neighbours[cw(e)]
    n = t.neighbours[e]
    # second check:
    # is vertex infinitely fast?
    # in this case, both sides of the new vertex 
    # should collapse to be infinitely fast!
    # is_inf_fast_a = is_infinitely_fast(get_fan(a, v2, cw), now)
    # is_inf_fast_b = is_infinitely_fast(get_fan(b, v1, ccw), now)
    # if is_inf_fast_a and is_inf_fast_b:
    #     assert kv is not None
    #     if not kv.inf_fast:
    #         logging.debug("New kinetic vertex: ***Not upgrading*** to infinitely fast moving vertex!")
            # we if the vertex is in a 90.0 degree angle for which both sides are inf-fast
            # kv.inf_fast = True
    # append to skeleton structure, new kinetic vertex
    skel.vertices.append(kv)

    # update circular list of kinetic vertices
    logging.debug("-- update circular list for new kinetic vertex kv: {} [{}]".format(id(kv), kv.info))
    update_circ(v1.left, kv, now)
    update_circ(kv, v2.right, now)
    # update the triangle fans incident
    fan_a = []
    fan_b = []
    if a is not None:
        logging.debug("- replacing vertex for neighbours at side A {} [{}]".format(id(a), a.info))
        a_idx = a.neighbours.index(t)
        a.neighbours[a_idx] = b
        fan_a = replace_kvertex(a, v2, kv, now, cw, queue, immediate)
        if pause:
            logging.debug('replaced neighbour A')
            interactive_visualize(queue, skel, step, now)

    if b is not None:
        logging.debug("- replacing vertex for neighbours at side B {} [{}]".format(id(b), b.info))
        b_idx = b.neighbours.index(t)
        b.neighbours[b_idx] = a
        fan_b = replace_kvertex(b, v1, kv, now, ccw, queue, immediate)
        if pause:
            logging.debug('replaced neighbour B')
            interactive_visualize(queue, skel, step, now)
    #
    logging.debug("*** neighbour n: {} ".format("schedule adjacent neighbour for *IMMEDIATE* processing" if n is not None else "no neighbour to collapse simultaneously"))
    if n is not None:
        n.neighbours[n.neighbours.index(t)] = None
        if n.event is not None and n.stops_at is None:
            schedule_immediately(n, now, queue, immediate)
    #visualize(queue, skel, now-1.0e-3)
    
    #raw_input('continue after parallel -- one of two legs')
    # process parallel fan, only if the fan has all un-dealt with triangles
    if kv and kv.inf_fast:
#        # fan - cw
#        if fan_a and all([t.stops_at is None for t in fan_a]):
#            handle_parallel_fan(fan_a, kv, now, cw, step, skel, queue, immediate, pause)
#            return
#        elif fan_a:
#            # we should have a fan in which all triangles are already stopped
#            assert all([t.stops_at is not None for t in fan_a])
#        # fan - ccw
#        if fan_b and all([t.stops_at is None for t in fan_b]):
#            handle_parallel_fan(fan_b, kv, now, ccw, step, skel, queue, immediate, pause)
#            return
#        elif fan_b:
#            # we should have a fan in which all triangles are already stopped
#            assert all([t.stops_at is not None for t in fan_b])

        if fan_a and fan_b:
            # combine both fans into 1
            fan_a = list(fan_a)
            fan_a.reverse()
            fan_a.extend(fan_b)
            handle_parallel_fan(fan_a, kv, now, ccw, step, skel, queue, immediate, pause)
            return
        elif fan_a:
            handle_parallel_fan(fan_a, kv, now, cw, step, skel, queue, immediate, pause)
            return
        elif fan_b:
            handle_parallel_fan(fan_b, kv, now, ccw, step, skel, queue, immediate, pause)
            return