def flip(self, t0, side0, t1, side1): """Performs the flip of triangle t0 and t1 If t0 and t1 are two triangles sharing a common edge AB, the method replaces ABC and BAD triangles by DCA and DBC, respectively. Pre-condition: triangles t0/t1 share a common edge and the edge is known """ self.flips += 1 apex0, orig0, dest0 = apex(side0), orig(side0), dest(side0) apex1, orig1, dest1 = apex(side1), orig(side1), dest(side1) # side0 and side1 should be same edge assert t0.vertices[orig0] is t1.vertices[dest1] assert t0.vertices[dest0] is t1.vertices[orig1] # assert both triangles have this edge unconstrained assert not t0.constrained[apex0] assert not t1.constrained[apex1] # -- vertices around quadrilateral in ccw order starting at apex of t0 A, B = t0.vertices[apex0], t0.vertices[orig0] C, D = t1.vertices[apex1], t0.vertices[dest0] # -- triangles around quadrilateral in ccw order, starting at A AB, BC = t0.neighbours[dest0], t1.neighbours[orig1] CD, DA = t1.neighbours[dest1], t0.neighbours[orig0] # link neighbours around quadrilateral to triangles as after the flip # -- the sides of the triangles around are stored in apex_around apex_around = [] for neighbour, corner in zip([AB, BC, CD, DA], [A, B, C, D]): if neighbour is None: apex_around.append(None) else: apex_around.append(ccw(neighbour.vertices.index(corner))) # the triangles around we link to the correct triangle *after* the flip for neighbour, side, t in zip([AB, BC, CD, DA], apex_around, [t0, t0, t1, t1]): if neighbour is not None: self.link_1dir(neighbour, side, t) # -- set new vertices and neighbours # for t0 t0.vertices = [A, B, C] t0.neighbours = [BC, t1, AB] # for t1 t1.vertices = [C, D, A] t1.neighbours = [DA, t0, CD] # -- update coordinate to triangle pointers for v in t0.vertices: v.triangle = t0 for v in t1.vertices: v.triangle = t1
def flip31(self, vertex): """ 'Flips' 3 triangles into 1, i.e. dissolves the three triangles around the pivot *vertex* into 1 triangle Pre-condition: the vertex is surrounded by exactly 3 triangles, this condition is _not_ checked """ # given a vertex # remove 2 of its adjacent triangles that also share an edge with this vertex # keeping 1 tri in the triangulation, # linking it to the two neighbours of # the 2 adjacent triangles that are removed # and hereby reducing the total number of triangles by two # # Note, these 2 triangles are garbage in the triangles list of the dt # and need to be garbage collected... similar to the vertex removed tri = vertex.triangle side0 = tri.vertices.index(vertex) apex0, orig0, dest0 = apex(side0), orig(side0), dest(side0) # the two triangles that will be removed ngb_orig = tri.neighbours[orig0] # neighbour tri opposite orig point ngb_dest = tri.neighbours[dest0] # neighbour tri opposite dest point # the 2 'around' triangles -- to link tri with! link_orig = ngb_orig.neighbours[ngb_orig.vertices.index(vertex)] link_dest = ngb_dest.neighbours[ngb_dest.vertices.index(vertex)] # the new 'apex' of the new triangle if link_orig: tip_orig = link_orig.vertices[dest( link_orig.vertices.index(tri.vertices[dest0]))] tri.vertices[apex0] = tip_orig if link_dest: tip_dest = link_dest.vertices[orig( link_dest.vertices.index(tri.vertices[orig0]))] tri.vertices[apex0] = tip_dest # extra check (in case we have both triangles present, # they should both refer to the same new apex) if link_orig and link_dest: assert tip_orig is tip_dest # link neighbours tri to link_orig if link_orig: self.link_2dir(tri, orig0, link_orig, orig(link_orig.vertices.index(tri.vertices[dest0]))) else: self.link_1dir(tri, orig0, None) # link neighbours tri to link_dest if link_dest: self.link_2dir(tri, dest0, link_dest, dest(link_dest.vertices.index(tri.vertices[orig0]))) else: self.link_1dir(tri, dest0, None) # fix triangle pointers of the vertices of the triangle for v in tri.vertices: v.triangle = tri # ngb_orig.neighbours = None ngb_orig.vertices = None ngb_orig.constrained = None ngb_orig.info = None # ngb_dest.neighbours = None ngb_dest.vertices = None ngb_dest.constrained = None ngb_dest.info = None # vertex.x = None vertex.y = None vertex.info = None vertex.triangle = None # FIXME: this will be slow for larger triangulations :'-( # note we could also leave the removed triangle/vertex objects in the list # and garbage collect them later, either when # we get too much garbage, or when we run a function # that depends on the triangles / vertices list in the triangulation object # (e.g. the output_* funcs) # or we remove the storage of the triangles / vertices in the list # completely and leave it up to the garbage collector of python # hybrid: keep them in the list, remove them based on own function call, # e.g. clean() self.triangulation.triangles.remove(ngb_orig) self.triangulation.triangles.remove(ngb_dest) self.triangulation.vertices.remove(vertex)
def remove(self, pt, ini): """Remove a point that is present in the triangulation, while keeping the triangulation Delaunay. The algorithm followed is from the paper by Mostafavi, Gold & Dakowicz (2003). @article{Mostafavi2003, doi = {10.1016/s0098-3004(03)00017-7}, url = {https://doi.org/10.1016/s0098-3004(03)00017-7}, year = {2003}, month = may, publisher = {Elsevier {BV}}, volume = {29}, number = {4}, pages = {523--530}, author = {Mir Abolfazl Mostafavi and Christopher Gold and Maciej Dakowicz}, title = {Delete and insert operations in Voronoi/Delaunay methods and applications}, journal = {Computers {\&} Geosciences} } Note 1: the removal does happily remove points if a CDT is used (not ok). Note 2: infinite vertex removal should not happen """ ### FIXME: This removal does not care about presence of constraints in the DT! ### Maybe we should make the Triangulation structure aware of this ### (either by subclassing or by having a bool attribute) tri = self._walk(ini, Vertex(pt[0], pt[1])) pivot = None for v in tri.vertices: if (v[0] == pt[0] and v[1] == pt[1]): pivot = v break del v if pivot is None: raise ValueError( "{} not found in triangulation, are you sure it was inserted?". format(pt)) # -- slice ears for the polygon around the pivot that is to be removed # the polygon around the pivot to be removed is represented by a # collection of triangles, which we call the *star* # the vertices on this polygon are called the *link* star = [edge.triangle for edge in StarEdgeIterator(pivot)] cur = 0 while len(star) > 3: # take 2 triangles (going around ccw around the pivot) tri0 = star[(cur) % len(star)] tri1 = star[(cur + 1) % len(star)] # get the vertices opposite of the pivot # tri0 side0 = tri0.vertices.index(pivot) v1 = tri0.vertices[orig(side0)] v2 = tri0.vertices[dest(side0)] # tri1 side1 = tri1.vertices.index(pivot) v_ = tri1.vertices[orig(side1)] v3 = tri1.vertices[dest(side1)] # they should share 1 vertex assert v2 is v_ #print(" ear to check --> {}".format( # " | ".join(map(lambda v: "{:8.2f} {:8.2f}".format(v.x, v.y), # [v1, v2, v3])))) tri2 = star[(cur + 2) % len(star)] # we have a potential ear to slice off # if (v1, v2, v3 turns left) and (v1, v3, pivot turns left or is straight) if orient2d(v1, v2, v3) > 0 and orient2d(v1, v3, pivot) >= 0: # make sure we really can slice the ear off slice_ear = True # check for other triangles (except tri0, tri1, tri2) its orig()-point # (i.e. circumcircle through ear its points is empty of other points in the link) for i in range(0, len(star) - 3): tri3 = star[(cur + 3 + i) % len(star)] assert tri3 is not tri0 assert tri3 is not tri1 assert tri3 is not tri2 v4 = tri3.vertices[orig(tri3.vertices.index(pivot))] if incircle(v1, v2, v3, v4) > 0: # v4 inside circle, do not slice slice_ear = False break if slice_ear: # -- Ear will be sliced, # flipping 2 triangles reduces the star by one triangle # # flip22 flips CCW # -> tri0 is thus the sliced ear, # so remove tri0 from the star and keep only tri1 in the remaining star self.flip22(tri0, tri0.vertices.index(v1), tri1, tri1.vertices.index(v3)) star.remove(tri0) # pivot should not be in tri0 its vertices assert pivot not in tri0.vertices # yet it should appear in tri1 assert pivot in tri1.vertices # if raw_input('#! write [y/N] $ ') in ('y', 'Y'): # with open("/tmp/all_tris.wkt", "w") as fh: # print(len(self.triangulation.triangles)) # output_triangles(self.triangulation.triangles, fh) # with open("/tmp/all_vertices.wkt", "w") as fh: # output_vertices(self.triangulation.vertices, fh) # increment cur += 1 # and -- possibly -- wrap around to start of list (make cur = 0) cur %= len(star) # with open("/tmp/tri__all_tris__before_flip31.wkt", "w") as fh: # output_triangles(self.triangulation.triangles, fh) # with open("/tmp/tri__all_vertices__before_flip31.wkt", "w") as fh: # output_vertices(self.triangulation.vertices, fh) # print(id(pivot.triangle), "to be removed") assert len(star) == 3 # -- now remove the 3 triangles by performing a flip3->1 self.flip31(pivot)