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
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))
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
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
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
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)
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
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
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
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)
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
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