Пример #1
0
// InsertVertex in the paper
static void insert_cavity_vertex_helper(MutableTriangleTopology& mesh, RawField<const Perturbed2,VertexId> X,
                                        RawField<bool,VertexId> marked, const HalfedgeId vw) {
  // If wv is a boundary edge, or we're already Delaunay and properly oriented, we're done
  const auto wv = mesh.reverse(vw);
  if (mesh.is_boundary(wv))
    return;
  const auto u = mesh.opposite(vw),
             v = mesh.src(vw),
             w = mesh.dst(vw),
             x = mesh.opposite(wv);
  const auto Xu = X[u],
             Xv = X[v],
             Xw = X[w],
             Xx = X[x];
  const bool in = incircle(Xu,Xv,Xw,Xx);
  if (!in && triangle_oriented(Xu,Xv,Xw))
    return;

  // Flip edge and recurse
  const auto xu = mesh.flip_edge(wv);
  assert(mesh.vertices(xu)==vec(x,u));
  const auto vx = mesh.prev(xu),
             xw = mesh.next(mesh.reverse(xu)); // Grab this now before the recursive call changes uvx
  insert_cavity_vertex_helper(mesh,X,marked,vx),
  insert_cavity_vertex_helper(mesh,X,marked,xw);
  if (!in)
    marked[u] = marked[v] = marked[w] = marked[x] = true;
}
Пример #2
0
static inline FaceId bsp_search(RawArray<const Node> bsp, RawField<const Perturbed2,VertexId> X, const VertexId v) {
  if (!bsp.size())
    return FaceId(0);
  int node = 0;
  do {
    const Node& n = bsp[node];
    node = n.children[triangle_oriented(X,n.test.x,n.test.y,v)];
  } while (node >= 0);
  return FaceId(~node);
}
Пример #3
0
GEODE_ALWAYS_INLINE static inline bool triangle_oriented(const RawField<const Perturbed2,VertexId> X, VertexId v0, VertexId v1, VertexId v2) {
  const auto x0 = X[v0],
             x1 = X[v1],
             x2 = X[v2];
  // If we have a nonsentinel triangle, use a normal orientation test
  if (maxabs(x0.value().x,x1.value().x,x2.value().x)!=bound)
    return triangle_oriented(x0,x1,x2);
  // Fall back to sentinel case analysis
  return triangle_oriented_sentinels(x0,x1,x2);
}
Пример #4
0
GEODE_COLD static void assert_delaunay(const char* prefix,
                                       const TriangleTopology& mesh, RawField<const Perturbed2,VertexId> X,
                                       const Hashtable<Vector<VertexId,2>>& constrained=Tuple<>(),
                                       const bool oriented_only=false,
                                       const bool check_boundary=true) {
  // Verify that all faces are correctly oriented
  for (const auto f : mesh.faces()) {
    const auto v = mesh.vertices(f);
    GEODE_ASSERT(triangle_oriented(X,v.x,v.y,v.z));
  }
  if (oriented_only)
    return;
  // Verify that all internal edges are Delaunay
  if (!constrained.size()) {
    for (const auto e : mesh.interior_halfedges())
      if (!mesh.is_boundary(mesh.reverse(e)) && mesh.src(e)<mesh.dst(e))
        if (!is_delaunay(mesh,X,e))
          throw RuntimeError(format("%snon delaunay edge: e%d, v%d v%d",prefix,e.id,mesh.src(e).id,mesh.dst(e).id));
  } else {
    for (const auto v : constrained)
      if (!mesh.halfedge(v.x,v.y).valid())
        throw RuntimeError(format("%smissing constraint edge: v%d v%d",prefix,v.x.id,v.y.id));
    for (const auto e : mesh.interior_halfedges()) {
      const auto s = mesh.src(e), d = mesh.dst(e);
      if (!mesh.is_boundary(mesh.reverse(e)) && s<d && !constrained.contains(vec(s,d)))
        if (!is_delaunay(mesh,X,e))
          throw RuntimeError(format("%snon delaunay edge: e%d, v%d v%d",prefix,e.id,mesh.src(e).id,mesh.dst(e).id));
    }
  }
  // Verify that all boundary vertices are convex
  if (check_boundary)
    for (const auto e : mesh.boundary_edges()) {
      const auto v0 = mesh.src(mesh.prev(e)),
                 v1 = mesh.src(e),
                 v2 = mesh.dst(e);
      GEODE_ASSERT(triangle_oriented(X,v2,v1,v0));
    }
}
Пример #5
0
// Test whether an edge containing sentinels is Delaunay
GEODE_COLD GEODE_PURE static inline bool is_delaunay_sentinels(Perturbed2 x0, Perturbed2 x1, Perturbed2 x2, Perturbed2 x3) {
  // Unfortunately, the sentinels need to be at infinity for purposes of Delaunay testing, and our SOS predicates
  // don't support infinities.  Therefore, we need some case analysis.  First, we move all the sentinels to the end,
  // sorted in decreasing order of index.  Different sentinels will be placed at different orders of infinity,
  // with earlier indices further away, so our order will place larger infinities last.
  bool parity = 0;
  COMPARATOR(0,1)
  COMPARATOR(2,3)
  COMPARATOR(0,2)
  COMPARATOR(1,3)
  COMPARATOR(1,2)
  if (!is_sentinel(x2)) // One infinity: A finite circle contains infinity iff it is inside out, so we reduce to an orientation test
    return parity^triangle_oriented(x0,x1,x2);
  else if (!is_sentinel(x1)) // Two infinities: also an orientation test, but with the last point at infinity
    return parity^segment_to_direction_oriented(x0,x1,x2);
  else // Three infinities: the finite point no longer matters.
    return parity^directions_oriented(x1,x2);
}
Пример #6
0
static void predicate_tests() {
  IntervalScope scope;
  typedef Vector<double,2> TV2;
  typedef Vector<Quantized,2> QV2;

  // Compare triangle_oriented and incircle against approximate floating point versions
  struct F {
    static inline double triangle_oriented(const TV2 p0, const TV2 p1, const TV2 p2) {
      return edet(p1-p0,p2-p0);
    };
    static inline double incircle(const TV2 p0, const TV2 p1, const TV2 p2, const TV2 p3) {
      const auto d0 = p0-p3, d1 = p1-p3, d2 = p2-p3;
      return edet(ROW(d0),ROW(d1),ROW(d2));
    }
  };
  const auto random = new_<Random>(9817241);
  for (int step=0;step<100;step++) {
    #define MAKE(i) \
      const auto p##i = tuple(i,QV2(random->uniform<Vector<ExactInt,2>>(-exact::bound,exact::bound))); \
      const TV2 x##i(p##i.y);
    MAKE(0) MAKE(1) MAKE(2) MAKE(3)
    GEODE_ASSERT(triangle_oriented(p0,p1,p2)==(F::triangle_oriented(x0,x1,x2)>0));
    GEODE_ASSERT(incircle(p0,p1,p2,p3)==(F::incircle(x0,x1,x2,x3)>0));
  }

  // Test behavior for large numbers, using the scale invariance and antisymmetry of incircle.
  for (const int i : range(exact::log_bound)) {
    const auto bound = ExactInt(1)<<i;
    const auto p0 = tuple(0,QV2(-bound,-bound)), // Four points on a circle of radius sqrt(2)*bound
               p1 = tuple(1,QV2( bound,-bound)),
               p2 = tuple(2,QV2( bound, bound)),
               p3 = tuple(3,QV2(-bound, bound));
    GEODE_ASSERT(!incircle(p0,p1,p2,p3));
    GEODE_ASSERT( incircle(p0,p1,p3,p2));
  }
}
Пример #7
0
GEODE_NEVER_INLINE static void add_constraint_edges(MutableTriangleTopology& mesh, RawField<const EV,VertexId> X,
                                                    RawArray<const Vector<int,2>> edges, const bool validate) {
  if (!edges.size())
    return;
  IntervalScope scope;
  Hashtable<Vector<VertexId,2>> constrained;
  Array<VertexId> left_cavity, right_cavity; // List of vertices for both cavities
  const auto random = new_<Random>(key+7);
  for (int i=0;i<edges.size();i++) {
    // Randomly choose an edge to ensure optimal time complexity
    const auto edge = edges[int(random_permute(edges.size(),key+5,i))].sorted();
    auto v0 = VertexId(edge.x),
         v1 = VertexId(edge.y);
    const auto vs = vec(v0,v1);
    GEODE_ASSERT(mesh.valid(v0) && mesh.valid(v1));

    {
      // Check if the edge already exists in the triangulation.  To ensure optimal complexity,
      // we loop around both vertices interleaved so that our time is O(min(degree(v0),degree(v1))).
      const auto s0 = mesh.halfedge(v0),
                 s1 = mesh.halfedge(v1);
      {
        auto e0 = s0,
             e1 = s1;
        do {
          if (mesh.dst(e0)==v1 || mesh.dst(e1)==v0)
            goto success; // The edge already exists, so there's nothing to be done.
          e0 = mesh.left(e0);
          e1 = mesh.left(e1);
        } while (e0!=s0 && e1!=s1);
      }

      // Find a triangle touching v0 or v1 containing part of the v0-v1 segment.
      // As above, we loop around both vertices interleaved.
      auto e0 = s0;
      {
        auto e1 = s1;
        if (mesh.is_boundary(e0)) e0 = mesh.left(e0);
        if (mesh.is_boundary(e1)) e1 = mesh.left(e1);
        const auto x0 = Perturbed2(v0.id,X[v0]),
                   x1 = Perturbed2(v1.id,X[v1]);
        const auto e0d = mesh.dst(e0),
                   e1d = mesh.dst(e1);
        bool e0o = triangle_oriented(x0,Perturbed2(e0d.id,X[e0d]),x1),
             e1o = triangle_oriented(x1,Perturbed2(e1d.id,X[e1d]),x0);
        for (;;) { // No need to check for an end condition, since we're guaranteed to terminate
          const auto n0 = mesh.left(e0),
                     n1 = mesh.left(e1);
          const auto n0d = mesh.dst(n0),
                     n1d = mesh.dst(n1);
          const bool n0o = triangle_oriented(x0,Perturbed2(n0d.id,X[n0d]),x1),
                     n1o = triangle_oriented(x1,Perturbed2(n1d.id,X[n1d]),x0);
          if (e0o && !n0o)
            break;
          if (e1o && !n1o) {
            // Swap v0 with v1 and e0 with e1 so that our ray starts at v0
            swap(v0,v1);
            swap(e0,e1);
            break;
          }
          e0 = n0;
          e1 = n1;
          e0o = n0o;
          e1o = n1o;
        }
      }

      // If we only need to walk one step, the retriangulation is a single edge flip
      auto cut = mesh.reverse(mesh.next(e0));
      if (mesh.dst(mesh.next(cut))==v1) {
        if (constrained.contains(vec(mesh.src(cut),mesh.dst(cut)).sorted()))
          throw DelaunayConstraintConflict(vec(v0.id,v1.id),vec(mesh.src(cut).id,mesh.dst(cut).id));
        cut = mesh.flip_edge(cut);
        goto success;
      }

      // Walk from v0 to v1, collecting the two cavities.
      const auto x0 = Perturbed2(v0.id,X[v0]),
                 x1 = Perturbed2(v1.id,X[v1]);
      right_cavity.copy(vec(v0,mesh.dst(cut)));
      left_cavity .copy(vec(v0,mesh.src(cut)));
      mesh.erase(mesh.face(e0));
      for (;;) {
        if (constrained.contains(vec(mesh.src(cut),mesh.dst(cut)).sorted()))
          throw DelaunayConstraintConflict(vec(v0.id,v1.id),vec(mesh.src(cut).id,mesh.dst(cut).id));
        const auto n = mesh.reverse(mesh.next(cut)),
                   p = mesh.reverse(mesh.prev(cut));
        const auto v = mesh.src(n);
        mesh.erase(mesh.face(cut));
        if (v == v1) {
          left_cavity.append(v);
          right_cavity.append(v);
          break;
        } else if (triangle_oriented(x0,x1,Perturbed2(v.id,X[v]))) {
          left_cavity.append(v);
          cut = n;
        } else {
          right_cavity.append(v);
          cut = p;
        }
      }

      // Retriangulate both cavities
      left_cavity.reverse();
      cavity_delaunay(mesh,X,left_cavity,random),
      cavity_delaunay(mesh,X,right_cavity,random);
    }
    success:
    constrained.set(vs);
  }

  // If desired, check that the final mesh is constrained Delaunay
  if (validate)
    assert_delaunay("constrained delaunay validate: ",mesh,X,constrained);
}
Пример #8
0
bool segments_intersect(const P2 a0, const P2 a1, const P2 b0, const P2 b1) {
  return triangle_oriented(a0,a1,b0)!=triangle_oriented(a0,a1,b1)
      && triangle_oriented(b0,b1,a0)!=triangle_oriented(b0,b1,a1);
}