Exemple #1
0
    def visibility_walk(self, ini, p):
        """Walk from triangle ini to triangle containing p

        Note, because this walk can cycle for a non-Delaunay triangulation
        we pick a random edge to continue the walk
        (this is a remembering stochastic walk, see RR-4120.pdf,
        Technical report from HAL-Inria by
        Olivier Devillers, Sylvain Pion, Monique Teillaud.
        Walking in a triangulation,
        https://hal.inria.fr/inria-00072509)

        For speed we do not check if we stay inside the bounding box
        that was used when initializing the triangulation, so make sure
        that a point given fits inside this box!
        """
        t = ini
        previous = None
        if t.vertices[2] is None:
            t = t.neighbours[2]
        n = len(self.triangulation.triangles)
        for ct in xrange(n):
            # get random side to continue walk, this way the walk cannot get
            # stuck by always picking triangles in the same order
            # (and get stuck in a cycle in case of non-Delaunay triangulation)
            e = randint(0, 2)
            if t.neighbours[e] is not previous and \
                orient2d(t.vertices[ccw(e)],
                         t.vertices[ccw(e+1)],
                         p) < 0:
                previous = t
                t = t.neighbours[e]
                continue
            e = ccw(e + 1)
            if t.neighbours[e] is not previous and \
                orient2d(t.vertices[ccw(e)],
                         t.vertices[ccw(e+1)],
                         p) < 0:
                previous = t
                t = t.neighbours[e]
                continue
            e = ccw(e + 1)
            if t.neighbours[e] is not previous and \
                orient2d(t.vertices[ccw(e)],
                         t.vertices[ccw(e+1)],
                         p) < 0:
                previous = t
                t = t.neighbours[e]
                continue
            self.visits += ct
            return t
        self.visits += ct
        return t
Exemple #2
0
 def _insert_vertex(self, u, v, w):
     """Insert a vertex to the triangulated area,
     while keeping the area of the current polygon triangulated
     """
     x = -1
     # Find third vertex in the triangle that has edge (w, v)
     if (w, v) in self.adjacency:
         x = self.adjacency[(w, v)]
     # See if we have to remove some triangle(s) already there,
     # or that we can add just a new one
     if x != -1 and \
         (orient2d(self.vertices[u],
                   self.vertices[v],
                   self.vertices[w]) <= 0 or
          incircle(self.vertices[u],
                   self.vertices[v],
                   self.vertices[w],
                   self.vertices[x]) > 0):
         # Remove triangle (w,v,x), also from adjacency dict
         self.triangles.remove(permute(w, v, x))
         del self.adjacency[(w, v)]
         del self.adjacency[(v, x)]
         del self.adjacency[(x, w)]
         # Recurse
         self._insert_vertex(u, v, x)
         self._insert_vertex(u, x, w)
     else:
         # Add a triangle (this triangle could be removed later)
         self._add_triangle(u, v, w)
Exemple #3
0
def triangle_overlaps_ray(vertex, towards):
    """Returns the triangle that overlaps the ray.
    In case there are multiple candidates,
    then the triangle with the right
    leg overlapping the ray is returned.
    It's a ValueError if no or multiple candidates are found.
    """
    candidates = []
    for edge in StarEdgeIterator(vertex):
        # if edge.isFinite:
        start, end = edge.segment
        # start: turns ccw
        # end: turns cw
        ostart = orient2d(start, towards, vertex)
        oend = orient2d(end, towards, vertex)
        if ostart >= 0 and oend <= 0:
            candidates.append((edge, ostart, oend))
    # the default, exactly one candidate
    if len(candidates) == 1:
        return candidates[0][0]
    # no candidates found,
    # this would be the case if towards lies outside
    # currently triangulated convex hull
    elif len(candidates) == 0:
        raise ValueError(
            "No overlap found (towards outside triangulated convex hull?)")
    # the ray overlaps the legs of multiple triangles
    # only return the triangle for which the right leg overlaps with the ray
    # it is an error if there is not exactly one candidate that we can return
    else:
        ostartct = 0
        candidateIdx = None
        for i, (edge, ostart, oend) in enumerate(candidates):
            if ostart == 0:
                ostartct += 1
                candidateIdx = i
        if ostartct != 1 or candidateIdx is None:
            for i, (edge, ostart, oend) in enumerate(candidates):
                print((ostart, oend))
            raise ValueError("Incorrect number of triangles found")
        return candidates[candidateIdx][0]
Exemple #4
0
 def straight_walk(self, ini, p):
     """Walk straight from triangle ini to triangle containing p"""
     tri = ini
     q_idx, r_idx, l_idx = 0, 1, 2
     # initialize the walk by rotating ccw or cw
     q = tri.vertices[q_idx]
     if orient2d(tri.vertices[r_idx], tri.vertices[q_idx], p) < 0:
         while orient2d(tri.vertices[l_idx], tri.vertices[q_idx], p) < 0:
             neighbour = tri.neighbours[r_idx]
             q_idx = neighbour.vertices.index(q)
             r_idx = ccw(q_idx)
             l_idx = ccw(r_idx)
             tri = neighbour
             assert tri.vertices[q_idx] == q
     else:
         while True:
             neighbour = tri.neighbours[l_idx]
             q_idx = neighbour.vertices.index(q)
             r_idx = ccw(q_idx)
             l_idx = ccw(r_idx)
             tri = neighbour
             assert neighbour.vertices[q_idx] == q
             if orient2d(tri.vertices[r_idx], tri.vertices[q_idx], p) < 0:
                 break
     # perform the walk
     s_idx = q_idx
     while orient2d(p, tri.vertices[r_idx], tri.vertices[l_idx]) < 0.0:
         neighbour = tri.neighbours[s_idx]
         l_idx = neighbour.vertices.index(tri.vertices[l_idx])
         r_idx = ccw(l_idx)
         s_idx = ccw(r_idx)
         tri = neighbour
         # 'advance' 1 of the two sides of the neighbour triangle
         # by swapping either l or r with s
         if orient2d(tri.vertices[s_idx], q, p) < 0.0:
             s_idx, r_idx = r_idx, s_idx
         else:
             s_idx, l_idx = l_idx, s_idx
     return tri
Exemple #5
0
 def _preprocess(self, cavity_edges):
     """Set up data structures needed for the re-triangulate part of the
     algorithm.
     """
     self.constraints = set()
     for i, edge in enumerate(cavity_edges):
         xx, yy = edge.segment
         # Both directions are needed, as this is used
         # for internal dangling edges inside the cavity,
         # which are traversed both sides.
         self.constraints.add((id(xx), id(yy)))
         self.constraints.add((id(yy), id(xx)))
         if i:
             self.vertices.append(yy)
         else:
             self.vertices.extend([xx, yy])
     # Make the vertices list COUNTERCLOCKWISE here
     # The algorithm depends on this orientation!
     self.vertices.reverse()
     self.surroundings = {}
     for i, edge in enumerate(cavity_edges):
         s = edge.segment
         self.surroundings[id(s[0]), id(s[1])] = edge
     # Make a "linked list" of polygon vertices
     self.next = {}
     self.prev = {}
     # Relative size of distances to the segment
     self.distance = {}
     # Adjacency: third point of a triangle by given oriented side
     self.adjacency = {}
     # Set of resulting triangles (vertex indices)
     self.triangles = set()
     # Initialization for the algorithm
     m = len(self.vertices)
     # Make random permutation of point indices
     self.pi = list(range(1, m - 1))
     # Randomize processing order
     shuffle(self.pi)
     # Link all vertices in a circular list that
     # describes the polygon outline of the cavity
     for i in range(m):
         self.next[i] = (i + 1) % m
         self.prev[i] = (i - 1) % m
         # Distance to the segment from [0-m]
         self.distance[i] = orient2d(self.vertices[0],
                                     self.vertices[i],
                                     self.vertices[m-1])
Exemple #6
0
def mark_cavity(P, Q, triangles):
    """Returns two lists: Edges above and below the list of triangles.
    These lists are sorted clockwise around the triangles
    (this is needed for CavityCDT).
    """
    # From a list of triangles make two lists of edges:
    # above and below...
    # It is made sure that the edges that are put
    # here are forming a polyline
    # that runs *clockwise* around the cavity
    # - to output the cavity:
#    with open('/tmp/cavity.wkt', 'w') as fh:
#         output_triangles(triangles, fh)
    assert len(triangles) != 0
    above = []
    below = []
    if len(triangles) == 1:
        t = triangles[0]
        pidx = t.vertices.index(P)
        lidx = (pidx + 1) % 3
        ridx = (pidx + 2) % 3
        lv = t.vertices[lidx]
        # r = t.vertices[ridx]
#         print "p", P
#         print "q", Q
#         print "lv", lv
#         print "r", r
        assert lv is Q
#         if lv is Q:
#             print "L IS Q"
        below = []
        for i in (ridx,):
            n = t.neighbours[i]
            b = Edge(n, n.neighbours.index(t))
            # b = Edge(n, common(t, i, n))
            below.append(b)
        above = []
        for i in (lidx, pidx,):
            n = t.neighbours[i]
            # b = Edge(n, common(t, i, n))
            b = Edge(n, n.neighbours.index(t))
            above.append(b)
#             below = [Edge(t.getNeighbour(pidx), t.getOppositeSide(pidx))]
#             above = [Edge(t.getNeighbour(ridx), t.getOppositeSide(ridx)),
#                      Edge(t.getNeighbour(lidx), t.getOppositeSide(lidx))]

#         elif r is Q:
#             print "R IS Q"
#             below = []
#             for i in (pidx, lidx,):
#                 n = t.neighbours[i]
#                 b = Edge(n, common(t, i, n))
#                 below.append(b)
#             above = []
#             for i in (ridx,):
#                 n = t.neighbours[i]
#                 b = Edge(n, common(t, i, n))
#                 above.append(b)
#             above = [Edge(t.getNeighbour(ridx), t.getOppositeSide(ridx))]
#             below = [Edge(t.getNeighbour(lidx), t.getOppositeSide(lidx)),
#                      Edge(t.getNeighbour(pidx), t.getOppositeSide(pidx))
#         else:
#             raise ValueError("Combinations exhausted")
    else:
        # precondition here is that triangles their legs
        # do NOT overlap with the segment that goes
        # from P -> Q
        # thus: left and right orientation cannot both be 0
        # -> this is an error
        for t in triangles:
            for side in range(3):
                # FIXME:
                # skip neighbouring side if it is none?
#                 if t.neighbours[side] is None:
#                     continue
                # FIXME: Do we add None into above/below???
                # if that is the case, we should still know which 2 infinite
                # vertices are between here...

                edge = Edge(t, side)
                R, L = edge.segment
                left = orient2d(L, Q, P)
                right = orient2d(R, Q, P)
                # in case both are 0 ... not allowed
                if (left == 0 and right == 0):
                    raise ValueError("Overlapping triangle leg found,"
                                     " not allowed")
                n = t.neighbours[side]
                e = Edge(n, n.neighbours.index(t))
                if left >= 0 and right >= 0:
                    below.append(e)
                elif right <= 0 and left <= 0:
                    above.append(e)

        below.reverse()
    return above, below
Exemple #7
0
    def _push_back_triangles(self):
        """Make new triangles that are inserted in the data structure
        and that are linked up properly with each other and the surroundings.
        """

        # First make new triangles
        newtris = {}
        for three in self.triangles:
            a, b, c, = three
            # Index triangle by temporary sorted list of vertex indices
            #
            # By indexing triangles this way, we
            T = Triangle(self.vertices[a],
                         self.vertices[b],
                         self.vertices[c])
            newtris[permute(id(T.vertices[0]), id(
                T.vertices[1]), id(T.vertices[2]))] = T
        for x in newtris.values():
            assert orient2d(x.vertices[0], x.vertices[1], x.vertices[2]) > 0
        # Translate adjacency table to new indices of triangles
        # Note that vertices that are used twice (because of dangling edge
        # in the cavity) will get the same identifier again
        # (while previously they would have different id's).
        adj = {}
        for (f, t), v in self.adjacency.items():
            adj[id(self.vertices[f]), id(self.vertices[t])] = id(
                self.vertices[v])
        # Link all the 3 sides of the new triangles properly
        for T in newtris.values():
            for i in range(3):
                segment = Edge(T, i).segment
                side = (id(segment[1]), id(segment[0]))
                constrained = False
                # The side is adjacent to another new triangle
                # The neighbouring triangle at this side will be linked later
                # In case this is a dangling segment we constrain the segment
                if side in adj:
                    neighbour = newtris[permute(side[0], side[1], adj[side])]
                    if side in self.constraints:
                        constrained = True
                # the side is adjacent to an exterior triangle
                # that lies outside the cavity and will
                # remain after the re-dt
                # therefore also change the neighbour of this triangle
                elif side in self.surroundings:
                    neighbour_side = self.surroundings[side].side
                    neighbour = self.surroundings[side].triangle
                    neighbour.neighbours[neighbour_side] = T  # MM fixed
                    # getEdgeType(neighbour_side)
                    constrained = neighbour.constrained[neighbour_side]
                # the triangle is the bottom of the evacuated cavity
                # hence it should be linked later to the other
                # re-dt of the cavity
                else:
                    if self.edge:
                        print((self.edge.triangle, self.edge.triangle.all_infinite, self.edge.triangle.__str__()))
                    assert self.edge is None
                    neighbour = None
                    self.edge = Edge(T, i)
                T.neighbours[i] = neighbour  # setNeighbour(i, neighbour)
                # T.setEdgeType(i, constrained)
                T.constrained[i] = constrained
            # Append the new triangles to the triangle list of the
            # dt
            self.dt.triangles.append(T)
        assert self.edge is not None
Exemple #8
0
def straight_walk(P, Q):
    """Obtain the list of triangles that overlap
    the line segment that goes from Vertex P to Q.

    Note that P and Q must be Vertex objects that are in the Triangulation
    already.

    Raises a ValueError when either a Constrained edge is crossed in the
    interior of the line segment or when another Vertex lies on the
    segment.
    """
    edge = triangle_overlaps_ray(P, Q)
    t = edge.triangle
    side = edge.side
    R, L = edge.segment
    out = [t]
    if Q in t.vertices:
        # we do not need to go into walking mode if we found
        # the exact triangle with the end point already
        return out
    # perform walk
    # pos = t.vertices.index(R)

    # from end via right to left makes right turn (negative)
    # if line is collinear with end point then orientation becomes 0

    # FIXME:
    # The way that we now stop the rotation around the vertex
    # does that make a problem here --> we can get either the lower
    # or the upper triangle, this depends on the arbitrary start triangle
    while orient2d(Q, R, L) < 0.:
        # check if we do not prematurely have a orientation of 0
        # at either side, which means that we collide a vertex
        if (L is not Q and orient2d(L, P, Q) == 0) or \
                (R is not Q and orient2d(R, P, Q) == 0):
            raise ValueError("Unwanted vertex collision detected - inserting: {} -> {} | crossing: {} -> {}". format(P, Q, R, L))

        # based on the position of R take next triangle
        # FIXME:
        # TEST THIS: check here if taking the neighbour does not take
        # place over a constrained side of the triangle -->
        # raise ValueError("Unwanted constrained segment collision detected")
#         if triangle.getEdgeType(side):
#             raise TopologyViolationError("Unwanted"
#                        " constrained segment collision detected")
        if t.constrained[side]:
            raise ValueError("Unwanted constrained segment collision detected - inserting: {} -> {} | crossing: {} -> {}". format(P, Q, R, L))
        t = t.neighbours[side]
        out.append(t)

        side = t.vertices.index(R)
        S = t.vertices[ccw(side)]
        ori = orient2d(S, Q, P)
        #
        if ori < 0:
            L = S
            side = ccw(side+1)
        else:
            R = S
        # check if we do not prematurely have a orientation of 0
        # at either side, which means that we collide a vertex
        if (L is not Q and orient2d(L, P, Q) == 0) or \
                (R is not Q and orient2d(R, P, Q) == 0):
            raise ValueError("Unwanted vertex collision detected - inserting: {} -> {} | crossing: {} -> {}". format(P, Q, R, L))

    return out
Exemple #9
0
 def is_ccw(self):
     return orient2d(self.vertices[0], self.vertices[1],
                     self.vertices[2]) > 0.
Exemple #10
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)