예제 #1
0
    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
예제 #2
0
    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)
예제 #3
0
    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)