Exemple #1
0
def at_same_location(V, now):
    """Checks whether all vertices are more or less at same location of first
    vertex in the list
    """
    P = [v.position_at(now) for v in V]
    logging.debug(P)
    p = P[0]
    for o in P[1:]:
        if not (near_zero(p[0] - o[0]) and near_zero(p[1] - o[1])):
            return False
    return True
Exemple #2
0
def replace_kvertex(t, v, newv, now, direction, queue, immediate):
    """Replace kinetic vertex at incident triangles

    Returns fan of triangles that were replaced
    """
    logging.debug("replace_kvertex, start at: {0} [{1}] dir: {2}".format(id(t), t.info, direction))
    fan = []
    first = True
    while t is not None:
        # assert t.stops_at is None, "{}: {}".format(
        #     id(t), [id(n) for n in t.neighbours])
        logging.debug(" @ {} [{}]".format(id(t), t.info))
        # FIXME:
        # if we have an event with the same time as now,
        # we should actually handle it
        logging.debug(t.event)
        if t.event is not None and near_zero(now - t.event.time):
            logging.debug(near_zero(now - t.event.time))
            logging.debug(""" 
            
            SAME SAME TIME... ARE WE PARALLEL?
            
            """)
            if t.event.tp == 'flip':
                logging.debug(t.neighbours[t.event.side[0]]) # -- can have become None
                # raw_input
                # if t.neighbours.count(None) < 2:
                logging.error('Error with current event -- as we do not handle flip now, we run the risk of inconsistency -- in fan: {0} $'.format(t.event))
                    # raw_input('paused $$')

        side = t.vertices.index(v)
        fan.append(t)
        t.vertices[side] = newv
        logging.debug(
            "Placed vertex #{} [{}] (inf fast? {}) at side {} of triangle {} [{}]".format(
                # id(newv),
                #repr(newv),
                id(newv),
                newv.info,
                newv.inf_fast, 
                side,
                id(t),
                t.info
                # , repr(t)
                ))
        if newv.inf_fast and t.event is not None:  # infinitely fast
            queue.discard(t.event)
            if t.event in immediate:
                immediate.remove(t.event)
        else:  # vertex moves with normal speed
            replace_in_queue(t, now, queue, immediate)
        t = t.neighbours[direction(side)]
    return tuple(fan)
Exemple #3
0
def eig_2x2(a, b, c, d):
    """Calculate eigenvalues and vectors of 2x2 matrix A, where:

         | a   b |
    A =  |       |
         | c   d |

     T = a + d
     D = a * d - b * c

     Eigenvalues:

     L1 = T/2 + (T**2/4 - D)**1/2
     L2 = T/2 - (T**2/4 - D)**1/2

     If c is not zero, then the eigenvectors are:

     | L1-d |   | L2-d |
     |      | , |      |
     |   c  |   |   c  |

     If b is not zero, then the eigenvectors are:

     |  b   |   |   b  |
     |      | , |      |
     | L1-a |   | L2-a |

     If both b and c are zero, then the eigenvectors are:

     |  1  |   |  0  |
     |     | , |     |
     |  0  |   |  1  |

     Note, the matrix is row-based!
    """
    T = a + d
    D = a * d - b * c
    tmp = 0.25 * T**2 - D
    tmp = math.sqrt(max(0, tmp))  # prevent negative square root
    L1 = 0.5 * T + tmp
    L2 = 0.5 * T - tmp
    if not near_zero(c):  # != 0
        eig_vecs = (L1 - d, c), (L2 - d, c)
    elif not near_zero(b):  # != 0
        eig_vecs = (b, L1 - a), (b, L2 - a)
    else:
        eig_vecs = (1.0, 0.0), (0.0, 1.0)
    eig_vals = map(abs, [L1, L2])  # is taking the absolute value needed?
    return eig_vals, eig_vecs
Exemple #4
0
def bisector(u1, u2):
    """Based on two unit vectors perpendicular to the wavefront,
    get the bisector

    The magnitude of the bisector vector represents the speed
    in which a vertex has to move to keep up (stay at the intersection of)
    the 2 wavefront edges
    """
    direction = add(u1, u2)
    logging.debug(" direction: {}".format(direction))
    d, acos_d = angle_unit(u1, u2)

    #    if all(map(near_zero, direction)) or near_zero(acos_d - math.pi): #
    if all(map(near_zero,
               direction)) or (near_zero(acos_d - math.pi)
                               or d < math.cos(math.radians(179.9999))):
        logging.debug(
            " vectors cancel each other out / angle ~180° -> parallel wavefront!"
        )
        return (0, 0)
        #raise ValueError("parallel wavefront")


###    logging.debug(" unit(direction): {}".format(unit(direction)))
    alpha = 0.5 * math.pi + 0.5 * acos_d
    logging.debug(" degrees(alpha): {}°".format(math.degrees(alpha)))
    magnitude = math.sin(alpha)
    logging.debug(" magnitude: {}".format(magnitude))
    # print magnitude
    bisector = div(unit(direction), magnitude)
    logging.debug(" bisector: {}".format(bisector))
    return bisector
Exemple #5
0
def check_active_triangles_orientation(tri, now):
    for t in tri:
        if t is None:
            continue
        if t.stops_at is None:
            pos = [t.vertices[side].position_at(now) for side in range(3)]
            ori = orient2d(*pos)
            if all(
                    isinstance(t.vertices[side], KineticVertex)
                    for side in range(3)):
                assert ori >= 0 or near_zero(
                    ori
                ), "triangle ({} [{}]) is not turning CCW @ t={}".format(
                    id(t), t.info, now)
            else:
                assert ori <= 0 or near_zero(
                    ori
                ), "inf-triangle ({} [{}]) is not turning CW @ t={}".format(
                    id(t), t.info, now)
Exemple #6
0
    def intersection_type(self):
        # solve intersection
        #
        one, other = self.one, self.other
        assert len(one.w) == 2
        assert len(other.w) == 2

        # == 3 possible outcomes in 2D:
        #
        # 0. overlapping lines - always intersecting in a line
        # 1. crossing - point2
        # 2. parallel - no intersection
        #
        (a1, b1), c1 = one.w, one.b
        (a2, b2), c2 = other.w, other.b
        denom = a1 * b2 - a2 * b1
        #        print(f'denom {denom}')
        # if denom == 0: # ^FIXME: use near_zero ?
        if near_zero(denom) == True:
            x1 = a1 * c2 - a2 * c1
            x2 = b1 * c2 - b2 * c1
            #            print(f'cross1 {x1}')
            #            print(f'cross2 {x2}')
            # if (x1 == 0) and (x2 == 0): # ^FIXME: use near_zero ?
            if near_zero(x1) == True and near_zero(x2) == True:
                # overlapping lines, always intersecting in this configuration
                self.result = self.one
                return LineLineIntersectionResult.LINE
            else:
                # parallel lines, but not intersecting in this configuration
                self.result = None
                return LineLineIntersectionResult.NO_INTERSECTION
        else:
            # crossing lines
            num1 = b1 * c2 - b2 * c1
            num2 = a2 * c1 - a1 * c2
            #            print(denom, num1, num2)
            x, y, w = num1, num2, denom
            xw = x / w
            yw = y / w
            #            print(f'point2 {xw}, {yw}')
            self.result = (xw, yw)
            return LineLineIntersectionResult.POINT
Exemple #7
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
Exemple #8
0
def regress(pts):
    """Return list of rotated points. The points are rotated in such a way
    that the direction of largest spread after rotation coincides with the
    positive x-axis, and the mean of the points is transformed to (0,0).
    If the original points formed a straight line, then the rotated points
    should have y-values that are all near zero.
    """
    # split points in list of x- and y-values
    xs, ys = split(pts)
    # adjust x- and y-values (subtract the mean)
    _, xsadj = adjust(xs)
    _, ysadj = adjust(ys)
    # calculate variances (spread) in x- and y-direction
    # If one of the two variances is (nearly) 0,
    # we short circuit our logic
    # -> points then have to be on a straight line
    # (either horizontal or vertical)
    sx = variance(xsadj, m=0)
    if near_zero(sx):  # == 0
        return ysadj, xsadj
    sy = variance(ysadj, m=0)
    if near_zero(sy):  # == 0
        return xsadj, ysadj
    # calculate covariance
    sxy = cov(xsadj, ysadj, 0, 0)
    # get list of eigenvalues and vectors
    # these are sorted based on size of eigenvalues
    eig_sorted = sorted(zip(*eig_2x2(sx, sxy, sxy, sy)), reverse=True)
    vecs = [vec for _, vec in eig_sorted]
    newxs, newys = [], []
    for pt in zip(xsadj, ysadj):
        # no need to transpose the vectors as they are
        xnew = vecs[0][0] * pt[0] + vecs[0][1] * pt[1]
        ynew = vecs[1][0] * pt[0] + vecs[1][1] * pt[1]
        newxs.append(xnew)
        # residuals?!
        # These should be squared distances to regression line that was fitted
        newys.append(ynew)
    return newxs, newys
Exemple #9
0
    def test_bump(self):
        """2 vertices that bump into each other half way"""
        u = KineticVertex()
        u.origin = (0., 0.)
        u.velocity = (1., 1.)

        v = KineticVertex()
        v.origin = (10., 10.)
        v.velocity = (-1., -1.)
        time = collapse_time_edge(u, v)
        assert time is not None
        dist = u.distance2_at(v, time)
        assert near_zero(dist)
Exemple #10
0
def show_all_times(times, o, d, a):
    print ""
    print times
    for time in times:
        pa = o.position_at(time)
        pb = d.position_at(time)
        pc = a.position_at(time)
        collapse = near_zero(orient2d(pa, pb, pc))
        print time, collapse
        if collapse:
            dists = [
                o.distance2_at(d, time),
                d.distance2_at(a, time),
                a.distance2_at(o, time)
            ]
            if all_close(dists, abs_tol=1e-8):
                print "point", pa, pb, pc
                avg = []
                for i in range(2):
                    avg.append(sum(map(lambda x: x[i], (pa, pb, pc))) / 3.)
                print " avg ", tuple(avg)
            else:
                print "line", pa, pb, pc
                # to a segment, two separate points
                # 1 distance is near_zero!
                # pa+pb = pc
                # pa+pc = pb
                # pb+pc = pa
                X = all_close_clusters([x for x, y in (pa, pb, pc)])
                Y = all_close_clusters([y for x, y in (pa, pb, pc)])
                print "     ", len(X)
                print "     ", len(Y)
                print "     ", X
                print "     ", Y
                # FIXME: by looking at the distances and knowledge on what
                # side is what, we should be able to reliable decide what
                # is the event that happens to this triangle!
                if len(X) == 3 or len(Y) == 3:
                    print "flip/split", dists
                else:
                    assert len(X) == 2 or len(Y) == 2
                    print "collapse", dists
                #
                # to a line, three separate points
                # no distance is near_zero!
                # -> could be flip or split event
    print ""
Exemple #11
0
def angle_unit(v1, v2):
    """angle between 2 *unit* vectors

    does not compute the norm(v1)*norm(v2), as it is assumed to be 1
    """
    d = dot(v1, v2)
    if d > 1.0 or d < -1.0:
        logging.debug("dot not in [-1, 1] -- clamp")
    d = max(-1.0, min(1.0, d))
    acos_d = math.acos(d)
    logging.debug(" d : {}".format(d))
    logging.debug(" acos(d) : {}".format(acos_d))
    logging.debug("  › near zero? {}".format(near_zero(acos_d - math.pi)))
    logging.debug(" degrees(acos(d)): {}°".format(math.degrees(acos_d)))
    logging.debug("  › d < cos(179.999)? {}".format(
        d < math.cos(math.radians(179.9999))))

    return d, acos_d
Exemple #12
0
    def test_crossing(self):
        """2 vertices crossing each other"""
        u = KineticVertex()
        u.origin = (0., 0.)
        u.velocity = (1., 1.)

        v = KineticVertex()
        v.origin = (10., 0.)
        v.velocity = (-1., 1.)
        time = collapse_time_edge(u, v)
        #         with open("/tmp/cpa.csv","w") as fh:
        #             for i, inc in enumerate(range(100, 150), start=1):
        #                 val = inc / 1000.
        #                 u.position_at(val)
        #                 v.position_at(val)
        #                 print >>fh, val,";", sqrt(u.distance2_at(v, val))
        dist = u.distance2_at(v, time)
        assert near_zero(dist)
Exemple #13
0
 def check_direct(event):
     """ check the direct neighbours whether they will collapse """
     current = event.triangle
     seen = set([current])
     visit = [current]
     also = []
     # FIXME: maybe we should use 'geometric inliers' to the support line here
     # this way we could also resolve 'flip event loops'
     while visit:
         current = visit.pop()
         for n in current.neighbours:
             if n is not None and n.event is not None and n not in seen:
                 seen.add(n)
                 if near_zero(n.event.time - event.time):
                     also.append(n)
                     visit.append(n)
             # elif n is not None and n.event is None:
             #     # not sure -- should we visit 'uncollapsable' triangles to check if their neighbours can collapse?
             #     seen.add(n)
             #     visit.append(n)
     if also:
         logging.debug([n.info for n in also])
Exemple #14
0
    def check_direct(event):
        """ check the direct neighbours whether they will collapse """

        current = event.triangle
        seen = set([current])
        visit = [current]
        also = []
        # FIXME: maybe we should use 'geometric inliers' to the support line here
        # this way we could also resolve 'flip event loops'
        while visit:
            current = visit.pop()
            for n in current.neighbours:
                if n is not None and n.event is not None and n not in seen:
                    seen.add(n)
                    if near_zero(n.event.time - event.time):
                        also.append(n)
                        visit.append(n)
                # elif n is not None and n.event is None:
                #     # not sure -- should we visit 'uncollapsable' triangles to check if their neighbours can collapse?
                #     seen.add(n)
                #     visit.append(n)
        events = [event] + [n.event for n in also]
        if events and all([_.tp == 'flip' for _ in events]):
            logging.debug(
                "Only flip events, picking flip event with longest side, should guarantee progress"
            )
            logging.debug(events)
            # # only flip events happening "now", pick event with longest side
            dist_events = []
            for evt in events:
                # compute distance of side to flip
                orig, dest = Edge(evt.triangle, evt.side[0]).segment
                dist = orig.distance2_at(dest, evt.time)
                dist_events.append((dist, evt))
            # take event with longest side
            dist_events.sort(reverse=True)
            return dist_events[0][1]
        else:
            return event
Exemple #15
0
def compute_new_kvertex(ul, ur, now, sk_node, info, internal, pause=False):
    """Based on the two wavefront directions and time t=now, compute the
    velocity and position at t=0 and return a new kinetic vertex

    Returns: KineticVertex
    """
    kv = KineticVertex()
    kv.info = info
    kv.starts_at = now
    kv.start_node = sk_node
    kv.internal = internal

    logging.debug('/=-= New vertex: {} [{}] =-=\\'.format(id(kv), kv.info))
    logging.debug('bisector calc')
    logging.debug(' ul: {}'.format(ul))
    logging.debug(' ur: {}'.format(ur))
    logging.debug(' sk_node.pos: {}'.format(sk_node.pos))

    # FIXME: only in pause mode!
    from grassfire.vectorops import angle_unit, add
    import math
    logging.debug(' >>> {}'.format(angle_unit(ul.w, ur.w)))
    u1, u2 = ul.w, ur.w
    direction = add(u1, u2)
    logging.debug(" direction: {}".format(direction))
    d, acos_d = angle_unit(u1, u2)

# FIXME: replace with new api: line.at_time(0).visualize()  //  line.at_time(t).visualize()
    if pause:
        with open('/tmpfast/support_lines.wkt', 'w') as fh:
            from grassfire.inout import interactive_visualize
            fh.write("wkt\tlr\toriginal")
            fh.write("\n")

            # -- bisector line --
            # b = ul.bisector(ur)
            b = ul.translated(mul(ul.w,now)).bisector( ur.translated(mul(ur.w,now)) )
            bperp = b.perpendicular(sk_node.pos)

            for l, lr, original in zip([ul, ur, ul.translated(mul(ul.w,now)), ur.translated(mul(ur.w,now)), b, bperp], ["l", "r", "l", "r", "b", "b"], [True, True, False, False, None, None]):
                fh.write(l.at_time(0).visualize())
                fh.write("\t")
                fh.write(lr)
                fh.write("\t")
                fh.write(str(original))
                fh.write("\n")


    # check for parallel wavefronts
    if all(map(near_zero, direction)) or near_zero(acos_d - math.pi) or d < math.cos(math.radians(179.999999)):
        logging.debug(" OVERRULED - vectors cancel each other out / angle ~180° -> parallel wavefront!")
        bi = (0, 0)
    else:
        from grassfire.line2d import LineLineIntersector, make_vector, LineLineIntersectionResult
        intersect = LineLineIntersector(ul, ur)
        tp = intersect.intersection_type()
        if tp == LineLineIntersectionResult.NO_INTERSECTION:
            bi = (0, 0)
        elif tp == LineLineIntersectionResult.POINT:
            pos_at_t0 = intersect.result
            ul_t = ul.translated(ul.w)
            ur_t = ur.translated(ur.w)
            intersect_t = LineLineIntersector(ul_t, ur_t)
            assert intersect_t.intersection_type() == 1
            bi = make_vector(end=intersect_t.result, start=pos_at_t0)
        elif tp == LineLineIntersectionResult.LINE:
            # this would mean original overlapping wavefronts... 
            # -> parallel at left / right of a kvertex
            # FIXME: would it be possible here to get to original position that defined the line?
            bi = tuple(ul.w[:])
            neg_velo = mul(mul(bi, -1.0), now)
            pos_at_t0 = add(sk_node.pos, neg_velo)

    kv.velocity = bi #was: bisector(ul, ur)
    logging.debug(' kv.velocity: {}'.format(kv.velocity))
    from grassfire.vectorops import norm
    magn_v = norm(kv.velocity)
    logging.debug(' magnitude of velocity: {}'.format(magn_v))
    logging.debug('\=-= New vertex =-=/')

    # if magn_v > 1000000:
    #     logging.debug(" OVERRULED - super fast vertex angle ~180° -> parallel wavefront!")
    #     kv.velocity = (0,0)

    
    # compute where this vertex would have been at time t=0
    # we set this vertex as infinitely fast, if velocity in one of the
    # directions is really high, or when the bisectors of adjacent
    # wavefronts cancel each other out
    if kv.velocity == (0, 0): ### or abs(kv.velocity[0]) > 100 or abs(kv.velocity[1]) > 100:
        kv.inf_fast = True
        kv.origin = sk_node.pos
    else:
#        neg_velo = mul(mul(kv.velocity, -1.0), now)
#        pos_at_t0 = add(sk_node.pos, neg_velo)
        kv.origin = pos_at_t0
    kv.ul = ul
    kv.ur = ur
    return kv
Exemple #16
0
def stop_kvertices(V, step, now, pos=None):
    """ Stop a list of kinetic vertices *V* at time *now*, creating a new node.

    If one of the vertices was already stopped before, at a node, use that
    skeleton node

    Returns tuple of (new node, False) in case all vertices are stopped for the
    first time, otherwise it returns (node, True) to indicate that were already
    stopped once.
    """
    # precondition:
    # the kinetic vertices that we are stopping should be
    # at more or less the same location
#     assert at_same_location(V, now)
    sk_node = None

    logging.debug("stopping kinetic vertices, @t:={}".format(now))
    for v in V:
        logging.debug(" - kv #{} [{}] inf-fast:={}".format(id(v), v.info, v.inf_fast))

    for v in V:
        stopped = v.stops_at is not None
        time_close = near_zero(v.starts_at - now)
        logging.debug("Vertex starts at same time as now: {}".format(time_close))
        logging.debug("Kinetic vertex is not stopped: {}".format(stopped))
        # vertex already stopped
        if stopped:
            logging.debug("Stop_node of vertex")
            sk_node = v.stop_node
        elif time_close:
            logging.debug("Time close")
            # FIXME: for the parallel case this code is problematic
            # as start and end point will be at same time
            assert not stopped
            sk_node = v.start_node
        else:
            v.stops_at = now
    if sk_node is not None:
        logging.debug("Skeleton node already there")
        for v in V:
            v.stop_node = sk_node
            v.stops_at = now
            # FIXME: it is not always true that vertices stopped this way
            # are at the same location (they are close, but because of
            # numerical issues can be on slightly different location
            # assert at_same_location([v, sk_node], now)
        is_new_node = False
    else:
        if pos is None:
            logging.debug("Make new skeleton node")
            l = [v.position_at(now) for v in V]
            ct = len(l)
            sumx, sumy = 0., 0.
            for x, y in l:
                sumx += x
                sumy += y
            pos = sumx / ct, sumy / ct
#        for x, y in l:
#            dx, dy = near_zero(x - pos[0]), near_zero(y - pos[1])
#            assert dx, x - pos[0]
#            assert dy, y - pos[1]
        else:
            logging.debug("Make new skeleton node - using external position: {}".format(pos))
        sk_node = SkeletonNode(pos, step)
        for v in V:
            v.stop_node = sk_node
        is_new_node = True
    # post condition
    # all vertices do have a stop node and are stopped at a certain time
    for v in V:
        assert v.stop_node is not None
        assert v.is_stopped == True
        assert v.stops_at == now

    logging.debug("POINT({0[0]} {0[1]});sknode_new_pos".format(sk_node.pos))

        # the geometric embedding of the vertex and its direction should correspond
        # only check when speed is relatively small
###        if abs(v.velocity[0]) < 100 and abs(v.velocity[1]) < 100:
###            d = dist(
###                v.stop_node.position_at(v.stops_at),
###                v.position_at(v.stops_at),
###            )
###            assert is_close(d, 0.0, rel_tol=1e-2, abs_tol=1e-2, method="weak"), \
###            "mis-match between position of skeleton and position of kinetic vertex at time:" \
###            " {}; vertex {} [{}]; dist:={}, v.velocity={}".format(v.stops_at, id(v), v.info, d, v.velocity)

        # FIXME:
        # should all segments in the skeleton have length
        # or do we keep a topological tree of events (where nodes
        # can be embedded at same location) ???
        # assert not at_same_location([v.start_node, v.stop_node], now), "stopped nodes should be different, but are not for {0}".format(id(v))
    return sk_node, is_new_node
Exemple #17
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
Exemple #18
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)
Exemple #19
0
def choose_next_event(queue):
    """Choose a next event from the queue

    In case we have all flip events, we pick the longest edge to flip

    *Incorrect:*
    In case there are multiple events happening more or less at the same time
    we pick a non-flipping event first. -- This is not okay
    -> we do not handle items in their correct order, and hence this leads to problems
    """

    # it = iter(queue)
    # first = next(it)
    # events = [first]
    # item = events[0]
    # queue.remove(item)
    # return item

    # # return the first item, sorted on type (non-flipping events first)
    # for item in sorted(events, key=sort_key):
    #     break

    def sort_key(evt):
        """Key for sorting; first by event type, when these equal by time"""
        types = {'flip': 2, 'split': 1, 'edge': 0}
        #         types = {'flip': 0, 'split': 1, 'edge': 2}
        # x = evt.triangle.vertices[0].position_at(evt.time)[0]
        # Do sort by event type (first split, than edge, than flip)
        # in case multiple same types, then sort on time, if time is exactly the same, we sort on info (stable id of kinetic triangle)
        # (otherwise we might miss events that should happen)
        return (types[evt.tp], evt.time, -evt.triangle.type, evt.triangle.info)

    it = iter(queue)
    first = next(it)

    if False:
        events = [first]
        for e in it:
            # if e.tp == "flip" and
            if near_zero(e.time - first.time):
                events.append(e)
            else:
                break

        logging.debug("Multiple Events to pick next event from ...")
        # logging.debug("\n - " +
        #               "\n - ".join(map(str, events)))
        if len(events) == 1:
            # just one event happening now
            item = events[0]
        elif all([_.tp == 'flip' for _ in events]):

            logging.debug(
                "Only flip events, picking flip event with longest side, should guarantee progress"
            )

            # # only flip events happening "now", pick event with longest side
            dist_events = []
            for evt in events:
                # compute distance of side to flip
                orig, dest = Edge(evt.triangle, evt.side[0]).segment
                dist = orig.distance2_at(dest, evt.time)
                dist_events.append((dist, evt))
            # take event with longest side
            dist_events.sort(reverse=True)
            item = dist_events[0][1]
            # item = events[0]
        else:
            ##        pts = []
            ##        for evt in events:
            ##            pts.extend([v.position_at(first.time)
            ##                        for v in evt.triangle.vertices])
            ##        on_straight_line = are_residuals_near_zero(pts)
            ##         print pts
            ##         for pt in pts:
            ##             print "POINT({0[0]} {0[1]})".format(pt)
            ##         metric = r_squared(pts)
            ##         logging.debug('metric {0}'.format(metric))
            ## #        import numpy as np
            ##         x = np.array([pt[0] for pt in pts])
            ##         y = np.array([pt[1] for pt in pts])
            ##         A = np.vstack([x, np.ones(len(x))]).T
            ##         res = np.linalg.lstsq(A, y)
            ##         print res

            # if not on_straight_line:
            #     logging.debug('pick first event')
            #     # points should be on straight line to exhibit infinite flipping
            #     # if this is not the case, we return the first event
            #     item = events[0]
            # else:
            #        logging.debug('pick non-flip event (vertices on straight line)')

            ##item = events[0]

            # # return the first item, sorted on type (non-flipping events first)
            for item in sorted(events, key=sort_key):
                break

    ## FIXME:
    ## BUG: this code leads to problems in removing events from the queue
    ## my assumption is that:
    ## 1. we can navigate onto a triangle that already has been dealt with
    ## 2. the event on the triangle has not been updated properly (old event)
    def check_direct(event):
        """ check the direct neighbours whether they will collapse """

        current = event.triangle
        seen = set([current])
        visit = [current]
        also = []
        # FIXME: maybe we should use 'geometric inliers' to the support line here
        # this way we could also resolve 'flip event loops'
        while visit:
            current = visit.pop()
            for n in current.neighbours:
                if n is not None and n.event is not None and n not in seen:
                    seen.add(n)
                    if near_zero(n.event.time - event.time):
                        also.append(n)
                        visit.append(n)
                # elif n is not None and n.event is None:
                #     # not sure -- should we visit 'uncollapsable' triangles to check if their neighbours can collapse?
                #     seen.add(n)
                #     visit.append(n)
        events = [event] + [n.event for n in also]
        if events and all([_.tp == 'flip' for _ in events]):
            logging.debug(
                "Only flip events, picking flip event with longest side, should guarantee progress"
            )
            logging.debug(events)
            # # only flip events happening "now", pick event with longest side
            dist_events = []
            for evt in events:
                # compute distance of side to flip
                orig, dest = Edge(evt.triangle, evt.side[0]).segment
                dist = orig.distance2_at(dest, evt.time)
                dist_events.append((dist, evt))
            # take event with longest side
            dist_events.sort(reverse=True)
            return dist_events[0][1]
        else:
            return event

    evt = first
    queue.remove(evt)

    # evt = check_direct(evt)
    # try:
    #     queue.remove(evt)
    # except:
    #     print(evt)
    #     raise
    return evt