示例#1
0
Nested<CircleArc> canonicalize_circle_arcs(Nested<const CircleArc> polys) {
  // Find the minimal point in each polygon under lexicographic order
  Array<int> mins(polys.size());
  for (int p=0;p<polys.size();p++) {
    const auto poly = polys[p];
    for (int i=1;i<poly.size();i++)
      if (lex_less(poly[i].x,poly[mins[p]].x))
        mins[p] = i;
  }

  // Sort the polygons
  struct Order {
    Nested<const CircleArc> polys;
    RawArray<const int> mins;
    Order(Nested<const CircleArc> polys, RawArray<const int> mins)
      : polys(polys), mins(mins) {}
    bool operator()(int i,int j) const {
      return lex_less(polys(i,mins[i]).x,polys(j,mins[j]).x);
    }
  };
  Array<int> order = arange(polys.size()).copy();
  sort(order,Order(polys,mins));

  // Copy into new array
  Nested<CircleArc> new_polys(polys.sizes().subset(order).copy(),uninit);
  for (int p=0;p<polys.size();p++) {
    const int base = mins[order[p]];
    const auto poly = polys[order[p]];
    const auto new_poly = new_polys[p];
    for (int i=0;i<poly.size();i++)
      new_poly[i] = poly[(i+base)%poly.size()];
  }
  return new_polys;
}
示例#2
0
Nested<T> join_fragments_helper(const Nested<T>& fragments) {

  // Build array of endpoints
  Array<Vec2> X;
  for(const auto& f : fragments) {
    assert(!f.empty());
    X.append(get_endpoint(f.front()));
    X.append(get_endpoint(f.back()));
  }

  Array<Match> current_matches = match_endpoints(X);

  // At this point each element in current_matches should have the join to the next fragment
  // We need to walk these matches and build a single nested array for the result
  // We build fragments by directly appending to the flat array, and update the offset array once the fragment is complete
  Nested<T,false> result;
  for(const int seed_match : range(current_matches.size())) {
    int curr = seed_match;
    while(current_matches[curr].is_set()) {
      const bool reversed = (curr & 1) != 0; // Even ids are starts of fragments, odd ids are ends of fragments
      add_fragment(result, fragments[curr>>1], reversed);
      const int next = current_matches[curr^1].end; // Next fragment comes at opposite end of the current fragment
      current_matches[curr].reset(); // Reset matches for this fragment to mark it as traversed
      current_matches[curr^1].reset();
      curr = next;
    }
    // Add new offset for fragment (assuming we added anything)
    if(result.offsets.back() != result.flat.size()) {
      result.offsets.append(result.flat.size());
    }
  }

  return result.freeze();
}
示例#3
0
Ref<SparseMatrix> TriangleSubdivision::loop_matrix() const {
  if (loop_matrix_)
    return ref(loop_matrix_);
  // Build matrix of Loop subdivision weights
  Hashtable<Vector<int,2>,T> A;
  int offset = coarse_mesh->nodes();
  RawArray<const Vector<int,2> > segments = coarse_mesh->segment_soup()->elements;
  Nested<const int> neighbors = coarse_mesh->sorted_neighbors();
  Nested<const int> boundary_neighbors = coarse_mesh->boundary_mesh()->neighbors();
  unordered_set<int> corners_set(corners.begin(),corners.end());
  // Fill in node weights
  for (int i=0;i<offset;i++){
    if (!neighbors.valid(i) || !neighbors.size(i) || corners_set.count(i) || (boundary_neighbors.valid(i) && boundary_neighbors.size(i) && boundary_neighbors.size(i)!=2))
      A.set(vec(i,i),1);
    else if (boundary_neighbors.valid(i) && boundary_neighbors.size(i)==2) { // Regular boundary node
      A.set(vec(i,i),(T).75);
      for (int a=0;a<2;a++)
        A.set(vec(i,boundary_neighbors(i,a)),(T).125);
    } else { // Interior node
      RawArray<const int> ni = neighbors[i];
      T alpha = new_loop_alpha(ni.size());
      A.set(vec(i,i),alpha);
      T other = (1-alpha)/ni.size();
      for (int j : ni)
        A.set(vec(i,j),other);
    }
  }
  // Fill in edge values
  for (int s=0;s<segments.size();s++) {
    int i = offset+s;
    Vector<int,2> e = segments[s];
    RawArray<const int> n[2] = {neighbors.valid(e[0])?neighbors[e[0]]:RawArray<const int>(),
                                neighbors.valid(e[1])?neighbors[e[1]]:RawArray<const int>()};
    if (boundary_neighbors.valid(e[0]) && boundary_neighbors.valid(e[1]) && boundary_neighbors.size(e[0]) && boundary_neighbors.size(e[1]) && boundary_neighbors[e[0]].contains(e[1])) // Boundary edge
      for (int a=0;a<2;a++)
        A.set(vec(i,e[a]),(T).5);
    else if (n[0].size()==6 && n[1].size()==6) { // Edge between regular vertices
      int j = n[0].find(e[1]);
      int c[2] = {n[0][(j-1+n[0].size())%n[0].size()],
                  n[0][(j+1)%n[0].size()]};
      for (int a=0;a<2;a++) {
        A.set(vec(i,e[a]),(T).375);
        A.set(vec(i,c[a]),(T).125);
      }
    } else { // Edge between one or two irregular vertices
      T factor = n[0].size()!=6 && n[1].size()!=6 ?.5:1;
      for (int k=0;k<2;k++)
        if (n[k].size()!=6) {
          A[vec(i,e[k])] += factor*(1-new_loop_beta(n[k].size()));
          int start = n[k].find(e[1-k]);
          for (int j=0;j<n[k].size();j++)
            A[vec(i,n[k][(start+j)%n[k].size()])] += factor*new_loop_weight(n[k].size(),j);
        }
    }
  }
  // Finalize construction
  loop_matrix_ = new_<SparseMatrix>(A,vec(offset+segments.size(),offset));
  return ref(loop_matrix_);
}
void f() {
  const int N = 10;
  const int M = 15;
  Val Arr[N];
  for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
      int k = Arr[i].x + Arr[j].x;
      // The repeat is there to allow FileCheck to make sure the two variable
      // names aren't the same.
      int l = Arr[i].x + Arr[j].x;
    }
  }
  // CHECK: for (auto & [[VAR:[a-zA-Z_]+]] : Arr)
  // CHECK-NEXT: for (auto & [[INNERVAR:[a-zA-Z_]+]] : Arr)
  // CHECK-NEXT: int k = [[VAR]].x + [[INNERVAR]].x;
  // CHECK-NOT: int l = [[VAR]].x + [[VAR]].x;

  Val Nest[N][M];
  for (int i = 0; i < N; ++i) {
    for (int j = 0; j < M; ++j) {
      printf("Got item %d", Nest[i][j].x);
    }
  }
  // The inner loop is also convertible, but doesn't need to be converted
  // immediately. Update this test when that changes!
  // CHECK: for (auto & [[VAR:[a-zA-Z_]+]] : Nest)
  // CHECK-NEXT: for (int j = 0; j < M; ++j)
  // CHECK-NEXT: printf("Got item %d", [[VAR]][j].x);

  // Note that the order of M and N are switched for this test.
  for (int j = 0; j < M; ++j) {
    for (int i = 0; i < N; ++i) {
      printf("Got item %d", Nest[i][j].x);
    }
  }
  // CHECK-NOT: for (auto & {{[a-zA-Z_]+}} : Nest[i])
  // CHECK: for (int j = 0; j < M; ++j)
  // CHECK-NEXT: for (auto & [[VAR:[a-zA-Z_]+]] : Nest)
  // CHECK-NEXT: printf("Got item %d", [[VAR]][j].x);
  Nested<T> NestT;
  for (Nested<T>::iterator I = NestT.begin(), E = NestT.end(); I != E; ++I) {
    for (T::iterator TI = (*I).begin(), TE = (*I).end(); TI != TE; ++TI) {
      printf("%d", *TI);
    }
  }
  // The inner loop is also convertible, but doesn't need to be converted
  // immediately. Update this test when that changes!
  // CHECK: for (auto & [[VAR:[a-zA-Z_]+]] : NestT) {
  // CHECK-NEXT: for (T::iterator TI = ([[VAR]]).begin(), TE = ([[VAR]]).end(); TI != TE; ++TI) {
  // CHECK-NEXT: printf("%d", *TI);
}
示例#5
0
Nested<Vec2> compare_splitting_algorithms(Nested<const Vec2> polys, const int depth) {
  // Call into non-graph based implementation
  const auto t0 = get_time();
  const auto simple_result = canonicalize_polygons(split_polygons(polys, depth));
  const auto simple_time = get_time() - t0;
  // Call into graph based implementation
  const auto t1 = get_time();
  const auto graph_result = canonicalize_polygons(split_polygons_greater(polys,depth));
  const auto graph_time = get_time() - t1;
  std::cout << "Time using graph  : " << graph_time << ", Time without graph: " << simple_time << '\n'
            << format("input N: %d, output N: %d, graph/without: %f\n", polys.total_size(), graph_result.total_size(), graph_time/simple_time);

  // Orders of arguments to segment_segment_intersection can slightly alter constructed points
  // If minimal points found in canonicalize_polygons are different this check could fail (current test cases don't trigger this)
  // If this assert is only triggered when FORCE_CANONICAL_CONSTRUCTION_ARGUMENTS is false, it might be necessary to implement more robust matching
  GEODE_ASSERT(all_close(graph_result,simple_result));
  #if FORCE_CANONICAL_CONSTRUCTION_ARGUMENTS
  // With FORCE_CANONICAL_CONSTRUCTION_ARGUMENTS, results should be bit-for-bit identical
  GEODE_ASSERT(graph_result == simple_result);
  #endif

  return graph_result;
}
示例#6
0
static void random_circle_quantize_test(int seed) {
  auto r = new_<Random>(seed);
  {
    // First check that we can split without hitting any asserts
    const auto sizes = vec(1.e-3,1.e1,1.e3,1.e7);
    Nested<CircleArc, false> arcs;
    arcs.append(make_circle(Vec2(0,0),Vec2(1,0)));
    for(const auto& s : sizes) {
      for(int i = 0; i < 200; ++i) {
        arcs.append(make_circle(s*r->unit_ball<Vec2>(),s*r->unit_ball<Vec2>()));
      }
    }
    // Take the union and make sure we don't hit any asserts
    circle_arc_union(arcs);
  }
  {
    // Build a bunch of arcs that don't touch
    const auto log_options = vec(1.e-3,1.e-1,1.e1,1.e3);
    const auto max_bounds = Box<Vec2>(Vec2(0.,0.)).thickened(1.e1 * log_options.max());
    const real spacing = 1e-5*max_bounds.sizes().max();
    const real max_x = max_bounds.max.x;

    real curr_x = max_bounds.min.x;
    Nested<CircleArc, false> arcs;
    for(int i = 0; i < 50; ++i) {
      const real remaining = max_x - curr_x;
      if(remaining < spacing)
        break;
      const real log_choice = log_options[r->uniform<int>(0, log_options.size())];
      real next_r = r->uniform<real>(0., min(log_choice, remaining));
      arcs.append(make_circle(Vec2(curr_x, 0.),Vec2(curr_x+next_r, 0.)));
      curr_x += next_r + spacing;
    }
    // Take the union and make sure we don't hit any asserts
    auto unioned = circle_arc_union(arcs);

    // If range of sizes is very large, some arcs could be filtered out if they are smaller than quantization threshold...
    GEODE_ASSERT(unioned.size() <= arcs.size());
  }
}
void f() {
  const int N = 10;
  const int M = 15;
  Val Arr[N];
  for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
      int k = Arr[i].x + Arr[j].x;
      // The repeat is there to allow FileCheck to make sure the two variable
      // names aren't the same.
      int l = Arr[i].x + Arr[j].x;
    }
  }
  // CHECK-MESSAGES: :[[@LINE-8]]:3: warning: use range-based for loop instead
  // CHECK-MESSAGES: :[[@LINE-8]]:5: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & elem : Arr)
  // CHECK-FIXES-NEXT: for (auto & Arr_j : Arr)
  // CHECK-FIXES-NEXT: int k = elem.x + Arr_j.x;
  // CHECK-FIXES-NOT: int l = elem.x + elem.x;

  // The inner loop is also convertible, but doesn't need to be converted
  // immediately. FIXME: update this test when that changes.
  Val Nest[N][M];
  for (int i = 0; i < N; ++i) {
    for (int j = 0; j < M; ++j) {
      printf("Got item %d", Nest[i][j].x);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-5]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & elem : Nest)
  // CHECK-FIXES-NEXT: for (int j = 0; j < M; ++j)
  // CHECK-FIXES-NEXT: printf("Got item %d", elem[j].x);

  // Note that the order of M and N are switched for this test.
  for (int j = 0; j < M; ++j) {
    for (int i = 0; i < N; ++i) {
      printf("Got item %d", Nest[i][j].x);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-4]]:5: warning: use range-based for loop instead
  // CHECK-FIXES-NOT: for (auto & {{[a-zA-Z_]+}} : Nest[i])
  // CHECK-FIXES: for (int j = 0; j < M; ++j)
  // CHECK-FIXES-NEXT: for (auto & elem : Nest)
  // CHECK-FIXES-NEXT: printf("Got item %d", elem[j].x);

  // The inner loop is also convertible.
  Nested<T> NestT;
  for (Nested<T>::iterator I = NestT.begin(), E = NestT.end(); I != E; ++I) {
    for (T::iterator TI = (*I).begin(), TE = (*I).end(); TI != TE; ++TI) {
      printf("%d", *TI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-5]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & elem : NestT)
  // CHECK-FIXES-NEXT: for (T::iterator TI = (elem).begin(), TE = (elem).end(); TI != TE; ++TI)
  // CHECK-FIXES-NEXT: printf("%d", *TI);

  // The inner loop is also convertible.
  Nested<S> NestS;
  for (Nested<S>::const_iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    for (S::const_iterator SI = (*I).begin(), SE = (*I).end(); SI != SE; ++SI) {
      printf("%d", *SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-5]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (const auto & elem : NestS)
  // CHECK-FIXES-NEXT: for (S::const_iterator SI = (elem).begin(), SE = (elem).end(); SI != SE; ++SI)
  // CHECK-FIXES-NEXT: printf("%d", *SI);

  for (Nested<S>::const_iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    const S &s = *I;
    for (S::const_iterator SI = s.begin(), SE = s.end(); SI != SE; ++SI) {
      printf("%d", *SI);
      const_g(SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-7]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (const auto & s : NestS)

  for (Nested<S>::iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    S &s = *I;
    for (S::iterator SI = s.begin(), SE = s.end(); SI != SE; ++SI) {
      printf("%d", *SI);
      g(SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-7]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & s : NestS)

  Foo foo;
  for (Nested<S>::const_iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    const S &s = *I;
    for (S::const_iterator SI = s.begin(), SE = s.end(); SI != SE; ++SI) {
      printf("%d", *SI);
      foo.const_g(SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-7]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (const auto & s : NestS)

  for (Nested<S>::iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    S &s = *I;
    for (S::iterator SI = s.begin(), SE = s.end(); SI != SE; ++SI) {
      printf("%d", *SI);
      foo.g(SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-7]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & s : NestS)

}
示例#8
0
文件: ast.cpp 项目: hellige/ccs-cpp
 bool operator()(Nested &nested) const
   { return nested.resolveImports(resolver, loader, inProgress); }
示例#9
0
文件: ast.cpp 项目: hellige/ccs-cpp
 void operator()(Nested &nested) const
   { nested.addTo(buildContext, baseContext); }
void f() {
  const int N = 10;
  const int M = 15;
  Val Arr[N];
  for (int I = 0; I < N; ++I) {
    for (int J = 0; J < N; ++J) {
      int K = Arr[I].X + Arr[J].X;
      // The repeat is there to allow FileCheck to make sure the two variable
      // names aren't the same.
      int L = Arr[I].X + Arr[J].X;
    }
  }
  // CHECK-MESSAGES: :[[@LINE-8]]:3: warning: use range-based for loop instead
  // CHECK-MESSAGES: :[[@LINE-8]]:5: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & I : Arr)
  // CHECK-FIXES-NEXT: for (auto & J : Arr)
  // CHECK-FIXES-NEXT: int K = I.X + J.X;
  // CHECK-FIXES-NOT: int L = I.X + I.X;

  // The inner loop is also convertible, but doesn't need to be converted
  // immediately. FIXME: update this test when that changes.
  Val Nest[N][M];
  for (int I = 0; I < N; ++I) {
    for (int J = 0; J < M; ++J) {
      printf("Got item %d", Nest[I][J].X);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-5]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & I : Nest)
  // CHECK-FIXES-NEXT: for (int J = 0; J < M; ++J)
  // CHECK-FIXES-NEXT: printf("Got item %d", I[J].X);

  // Note that the order of M and N are switched for this test.
  for (int J = 0; J < M; ++J) {
    for (int I = 0; I < N; ++I) {
      printf("Got item %d", Nest[I][J].X);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-4]]:5: warning: use range-based for loop instead
  // CHECK-FIXES-NOT: for (auto & {{[a-zA-Z_]+}} : Nest[I])
  // CHECK-FIXES: for (int J = 0; J < M; ++J)
  // CHECK-FIXES-NEXT: for (auto & I : Nest)
  // CHECK-FIXES-NEXT: printf("Got item %d", I[J].X);

  // The inner loop is also convertible.
  Nested<T> NestT;
  for (Nested<T>::iterator I = NestT.begin(), E = NestT.end(); I != E; ++I) {
    for (T::iterator TI = (*I).begin(), TE = (*I).end(); TI != TE; ++TI) {
      printf("%d", *TI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-5]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & I : NestT)
  // CHECK-FIXES-NEXT: for (T::iterator TI = I.begin(), TE = I.end(); TI != TE; ++TI)
  // CHECK-FIXES-NEXT: printf("%d", *TI);

  // The inner loop is also convertible.
  Nested<S> NestS;
  for (Nested<S>::const_iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    for (S::const_iterator SI = (*I).begin(), SE = (*I).end(); SI != SE; ++SI) {
      printf("%d", *SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-5]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto I : NestS)
  // CHECK-FIXES-NEXT: for (S::const_iterator SI = I.begin(), SE = I.end(); SI != SE; ++SI)
  // CHECK-FIXES-NEXT: printf("%d", *SI);

  for (Nested<S>::const_iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    const S &Ss = *I;
    for (S::const_iterator SI = Ss.begin(), SE = Ss.end(); SI != SE; ++SI) {
      printf("%d", *SI);
      const_g(SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-7]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto Ss : NestS)

  for (Nested<S>::iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    S &Ss = *I;
    for (S::iterator SI = Ss.begin(), SE = Ss.end(); SI != SE; ++SI) {
      printf("%d", *SI);
      g(SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-7]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & Ss : NestS)

  Foo foo;
  for (Nested<S>::const_iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    const S &Ss = *I;
    for (S::const_iterator SI = Ss.begin(), SE = Ss.end(); SI != SE; ++SI) {
      printf("%d", *SI);
      foo.const_g(SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-7]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto Ss : NestS)

  for (Nested<S>::iterator I = NestS.begin(), E = NestS.end(); I != E; ++I) {
    S &Ss = *I;
    for (S::iterator SI = Ss.begin(), SE = Ss.end(); SI != SE; ++SI) {
      printf("%d", *SI);
      foo.g(SI);
    }
  }
  // CHECK-MESSAGES: :[[@LINE-7]]:3: warning: use range-based for loop instead
  // CHECK-FIXES: for (auto & Ss : NestS)

}
示例#11
0
Nested<EV> exact_split_polygons(Nested<const EV> polys, const int depth) {
  IntervalScope scope;
  RawArray<const EV> X = polys.flat;

  // We index segments by the index of their first point in X.  For convenience, we make an array to keep track of wraparounds.
  Array<int> next = (arange(X.size())+1).copy();
  for (int i=0;i<polys.size();i++) {
    GEODE_ASSERT(polys.size(i)>=3,"Degenerate polygons are not allowed");
    next[polys.offsets[i+1]-1] = polys.offsets[i];
  }

  // Compute all nontrivial intersections between segments
  struct Pairs {
    const BoxTree<EV>& tree;
    RawArray<const int> next;
    RawArray<const EV> X;
    Array<Vector<int,2>> pairs;

    Pairs(const BoxTree<EV>& tree, RawArray<const int> next, RawArray<const EV> X)
      : tree(tree), next(next), X(X) {}

    bool cull(const int n) const { return false; }
    bool cull(const int n0, const int box1) const { return false; }
    void leaf(const int n) const { assert(tree.prims(n).size()==1); }

    void leaf(const int n0, const int n1) {
      assert(tree.prims(n0).size()==1 && tree.prims(n1).size()==1);
      const int i0 = tree.prims(n0)[0], i1 = next[i0],
                j0 = tree.prims(n1)[0], j1 = next[j0];
      if (!(i0==j0 || i0==j1 || i1==j0 || i1==j1)) {
        const auto a0 = Perturbed2(i0,X[i0]), a1 = Perturbed2(i1,X[i1]),
                   b0 = Perturbed2(j0,X[j0]), b1 = Perturbed2(j1,X[j1]);
        if (segments_intersect(a0,a1,b0,b1))
          pairs.append(vec(i0,j0));
      }
    }
  };
  const auto tree = new_<BoxTree<EV>>(segment_boxes(next,X),1);
  Pairs pairs(tree,next,X);
  double_traverse(*tree,pairs);

  // Group intersections by segment.  Each pair is added twice: once for each order.
  Array<int> counts(X.size());
  for (auto pair : pairs.pairs) {
    counts[pair.x]++;
    counts[pair.y]++;
  }
  Nested<int> others(counts,uninit);
  for (auto pair : pairs.pairs) {
    others(pair.x,--counts[pair.x]) = pair.y;
    others(pair.y,--counts[pair.y]) = pair.x;
  }
  pairs.pairs.clean_memory();
  counts.clean_memory();

  // Walk all original polygons, recording which subsegments occur in the final result
  Hashtable<Vector<int,2>,int> graph; // If (i,j) -> k, the output contains the portion of segment j from ij to jk
  for (const int p : range(polys.size())) {
    const auto poly = range(polys.offsets[p],polys.offsets[p+1]);
    // Compute the depth of the first point in the polygon by firing a ray along the positive x axis.
    struct Depth {
      const BoxTree<EV>& tree;
      RawArray<const int> next;
      RawArray<const EV> X;
      const Perturbed2 start;
      int depth;

      Depth(const BoxTree<EV>& tree, RawArray<const int> next, RawArray<const EV> X, const int prev, const int i)
        : tree(tree), next(next), X(X)
        , start(i,X[i])
        // If we intersect no other segments, the depth depends on the orientation of direction = (1,0) relative to segments prev and i
        , depth(-!local_outwards_x_axis(Perturbed2(prev,X[prev]),start,Perturbed2(next[i],X[next[i]]))) {}

      bool cull(const int n) const {
        const auto box = tree.boxes(n);
        return box.max.x<start.value().x || box.max.y<start.value().y || box.min.y>start.value().y;
      }

      void leaf(const int n) {
        assert(tree.prims(n).size()==1);
        const int i0 = tree.prims(n)[0], i1 = next[i0];
        if (start.seed()!=i0 && start.seed()!=i1) {
          const auto a0 = Perturbed2(i0,X[i0]),
                     a1 = Perturbed2(i1,X[i1]);
          const bool above0 = upwards(start,a0),
                     above1 = upwards(start,a1);
          if (above0!=above1 && above1==triangle_oriented(a0,a1,start))
            depth += above1 ? 1 : -1;
        }
      }
    };
    Depth ray(tree,next,X,poly.back(),poly[0]);
    single_traverse(*tree,ray);

    // Walk around the polygon, recording all subsegments at the desired depth
    int delta = ray.depth-depth;
    int prev = poly.back();
    for (const int i : poly) {
      const int j = next[i];
      const Vector<Perturbed2,2> segment(Perturbed2(i,X[i]),Perturbed2(j,X[j]));
      const auto other = others[i];
      // Sort intersections along this segment
      if (other.size() > 1) {
        struct PairOrder {
          RawArray<const int> next;
          RawArray<const EV> X;
          const Vector<Perturbed2,2> segment;

          PairOrder(RawArray<const int> next, RawArray<const EV> X, const Vector<Perturbed2,2>& segment)
            : next(next), X(X), segment(segment) {}

          bool operator()(const int j, const int k) const {
            if (j==k)
              return false;
            const int jn = next[j],
                      kn = next[k];
            return segment_intersections_ordered(segment.x,segment.y,
                                                 Perturbed2(j,X[j]),Perturbed2(jn,X[jn]),
                                                 Perturbed2(k,X[k]),Perturbed2(kn,X[kn]));
          }
        };
        sort(other,PairOrder(next,X,segment));
      }
      // Walk through each intersection of this segment, updating delta as we go and remembering the subsegment if it has the right depth
      for (const int o : other) {
        if (!delta)
          graph.set(vec(prev,i),o);
        const int on = next[o];
        delta += segment_directions_oriented(segment.x,segment.y,Perturbed2(o,X[o]),Perturbed2(on,X[on])) ? -1 : 1;
        prev = o;
      }
      if (!delta)
        graph.set(vec(prev,i),next[i]);
      // Advance to the next segment
      prev = i;
    }
  }

  // Walk the graph to produce output polygons
  Hashtable<Vector<int,2>> seen;
  Nested<EV,false> output;
  for (const auto& start : graph)
    if (seen.set(start.x)) {
      auto ij = start.x;
      for (;;) {
        const int i = ij.x, j = ij.y, in = next[i], jn = next[j];
        output.flat.append(j==next[i] ? X[j] : segment_segment_intersection(Perturbed2(i,X[i]),Perturbed2(in,X[in]),Perturbed2(j,X[j]),Perturbed2(jn,X[jn])));
        ij = vec(j,graph.get(ij));
        if (ij == start.x)
          break;
        seen.set(ij);
      }
      output.offsets.append(output.flat.size());
    }
  return output;
}