TraceMaker
C++ API of the engine
Loading...
Searching...
No Matches
region.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-3.0-or-later
2#pragma once
3// The reference copper of one layer as a queryable region (issue #7, docs/05-routing.md ยง42): the geometry of the return-path audit.
4#include <algorithm>
5#include <cmath>
6#include <cstdint>
7#include <cstdlib>
8#include <limits>
9#include <vector>
10
11#include "geom/clip.hpp"
12#include "geom/point.hpp"
13
15
16using geom::Point;
17using geom::Ring;
18using geom::Rings;
19
20struct Pt {
21 double x = 0, y = 0;
22};
23inline Pt to_pt(Point p) { return {static_cast<double>(p.x), static_cast<double>(p.y)}; }
24inline Point to_point(Pt p) { return {static_cast<Coord>(std::llround(p.x)), static_cast<Coord>(std::llround(p.y))}; }
25inline double dist(Pt a, Pt b) { return std::hypot(a.x - b.x, a.y - b.y); }
26
27// The reference copper of one layer: the region under the non-zero rule (islands with their holes). Its edges and vertices are bucketed in
28// grids so that "is this point on copper?", "can a segment cross it?" and "which vertices are near?" cost the neighbourhood and not the whole
29// pour (the plane of a large board has hundreds of thousands of edges, most of them antipads), and the vertices a shortest path can bend at.
30class Region {
31 public:
32 Region() = default;
33 explicit Region(const Rings& rings) {
34 int island = 0;
35 for (const auto& poly : geom::polygons(rings)) {
36 auto add = [&](const Ring& r) {
37 for (std::size_t i = 0; i < r.size(); ++i) {
38 edges_.push_back({to_pt(r[i]), to_pt(r[(i + 1) % r.size()]), island});
39 verts_.push_back(to_pt(r[i]));
40 }
41 };
42 add(poly.outer);
43 for (const auto& h : poly.holes) add(h);
44 ++island;
45 }
46 for (const auto& e : edges_) {
47 box_.add(to_point(e.a));
48 box_.add(to_point(e.b));
49 }
50 if (box_.empty()) return;
51 nx_ = static_cast<int>((box_.x1 - box_.x0) / kCell) + 1;
52 ny_ = static_cast<int>((box_.y1 - box_.y0) / kCell) + 1;
53 nrows_ = static_cast<int>((box_.y1 - box_.y0) / kRow) + 1;
54 cells_.assign(static_cast<std::size_t>(nx_) * static_cast<std::size_t>(ny_), {});
55 vcells_.assign(cells_.size(), {});
56 rows_.assign(static_cast<std::size_t>(nrows_), {});
57 for (std::size_t i = 0; i < edges_.size(); ++i) {
58 const Edge& e = edges_[i];
59 const int x0 = cell_x(std::min(e.a.x, e.b.x)), x1 = cell_x(std::max(e.a.x, e.b.x));
60 const int y0 = cell_y(std::min(e.a.y, e.b.y)), y1 = cell_y(std::max(e.a.y, e.b.y));
61 for (int cy = y0; cy <= y1; ++cy)
62 for (int cx = x0; cx <= x1; ++cx) cells_[idx(cx, cy)].push_back(static_cast<int>(i));
63 if (e.a.y == e.b.y) continue; // a horizontal edge never crosses a horizontal ray
64 for (int r = row_of(std::min(e.a.y, e.b.y)); r <= row_of(std::max(e.a.y, e.b.y)); ++r) rows_[static_cast<std::size_t>(r)].push_back(static_cast<int>(i));
65 }
66 for (std::size_t i = 0; i < verts_.size(); ++i) vcells_[idx(cell_x(verts_[i].x), cell_y(verts_[i].y))].push_back(static_cast<int>(i));
67 stamp_.assign(edges_.size(), 0);
68 }
69
70 bool empty() const { return edges_.empty(); }
71
72 // Inside the region (on its boundary counts as inside: a return may run along it).
73 bool contains(Pt p) const { return locate(p) >= 0; }
74 // Which island of copper (an outer ring with its holes) the point is on, -1 if none: two points on different islands have no path between them.
75 int island_of(Pt p) const { return locate(p); }
76
77 // The vertices a path no longer than `bound` between a and b could bend at: inside the ellipse with foci a and b.
78 void vertices_near(Pt a, Pt b, double bound, std::vector<Pt>& out) const {
79 if (box_.empty()) return;
80 const double cx = (a.x + b.x) / 2, cy = (a.y + b.y) / 2, r = bound / 2;
81 const int x0 = cell_x(cx - r), x1 = cell_x(cx + r), y0 = cell_y(cy - r), y1 = cell_y(cy + r);
82 for (int yy = y0; yy <= y1; ++yy)
83 for (int xx = x0; xx <= x1; ++xx)
84 for (int vi : vcells_[idx(xx, yy)]) {
85 const Pt& v = verts_[static_cast<std::size_t>(vi)];
86 if (dist(a, v) + dist(v, b) <= bound) out.push_back(v);
87 }
88 }
89
90 // Is the open segment a-b free of properly crossing edges? Touching an edge at a vertex or running along it is allowed.
91 bool crosses(Pt a, Pt b) const {
92 if (box_.empty()) return false;
93 ++clock_;
94 const int x0 = cell_x(std::min(a.x, b.x)), x1 = cell_x(std::max(a.x, b.x)), y0 = cell_y(std::min(a.y, b.y)), y1 = cell_y(std::max(a.y, b.y));
95 for (int cy = y0; cy <= y1; ++cy)
96 for (int cx = x0; cx <= x1; ++cx)
97 for (int ei : cells_[idx(cx, cy)]) {
98 if (stamp_[static_cast<std::size_t>(ei)] == clock_) continue;
99 stamp_[static_cast<std::size_t>(ei)] = clock_;
100 if (proper_cross(a, b, edges_[static_cast<std::size_t>(ei)].a, edges_[static_cast<std::size_t>(ei)].b)) return true;
101 }
102 return false;
103 }
104
105 // The segment lies in the region: no edge crosses it, and every piece of it between two touches of the boundary is inside (a segment
106 // that runs along the boundary and over a gap in it would pass a midpoint test).
107 bool sees(Pt a, Pt b) const {
108 if (crosses(a, b)) return false;
109 std::vector<double> ts{0.0, 1.0};
110 const double dx = b.x - a.x, dy = b.y - a.y, len2 = dx * dx + dy * dy;
111 if (len2 < 1.0) return contains(a);
112 auto along = [&](Pt p, double& t) { // p on the segment (within 3 nm)? its parameter
113 t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;
114 if (t <= 0 || t >= 1) return false;
115 return dist(p, {a.x + t * dx, a.y + t * dy}) < 3.0;
116 };
117 if (!box_.empty()) {
118 ++clock_;
119 const int x0 = cell_x(std::min(a.x, b.x)), x1 = cell_x(std::max(a.x, b.x)), y0 = cell_y(std::min(a.y, b.y)), y1 = cell_y(std::max(a.y, b.y));
120 for (int cy = y0; cy <= y1; ++cy)
121 for (int cx = x0; cx <= x1; ++cx)
122 for (int ei : cells_[idx(cx, cy)]) {
123 if (stamp_[static_cast<std::size_t>(ei)] == clock_) continue;
124 stamp_[static_cast<std::size_t>(ei)] = clock_;
125 const Edge& e = edges_[static_cast<std::size_t>(ei)];
126 double t = 0;
127 if (along(e.a, t)) ts.push_back(t);
128 if (along(e.b, t)) ts.push_back(t);
129 const double ex = e.b.x - e.a.x, ey = e.b.y - e.a.y, den = dx * ey - dy * ex;
130 if (std::fabs(den) > 1e-9 * std::sqrt(len2) * std::hypot(ex, ey)) {
131 const double tt = ((e.a.x - a.x) * ey - (e.a.y - a.y) * ex) / den, uu = ((e.a.x - a.x) * dy - (e.a.y - a.y) * dx) / den;
132 if (tt > 0 && tt < 1 && uu >= 0 && uu <= 1) ts.push_back(tt);
133 }
134 }
135 }
136 std::sort(ts.begin(), ts.end());
137 for (std::size_t i = 1; i < ts.size(); ++i) {
138 if ((ts[i] - ts[i - 1]) * std::sqrt(len2) < 2.0) continue; // a piece shorter than 2 nm
139 const double m = (ts[i] + ts[i - 1]) / 2;
140 if (!contains({a.x + m * dx, a.y + m * dy})) return false;
141 }
142 return true;
143 }
144
145 // The point of the region's boundary nearest to p, within `reach`; false if none. Cells are searched in growing squares. Several edges can be
146 // exactly as near (a pad at the centre of a round antipad): the one that comes first in the region's edge order wins, so the answer does not
147 // depend on the order the grid is walked in.
148 bool nearest_boundary(Pt p, double reach, Pt& out) const {
149 if (box_.empty()) return false;
150 double best = reach;
151 int best_edge = -1;
152 const int c0x = cell_x(p.x), c0y = cell_y(p.y), maxr = static_cast<int>(reach / static_cast<double>(kCell)) + 1;
153 for (int r = 0; r <= maxr; ++r) {
154 for (int cy = std::max(0, c0y - r); cy <= std::min(ny_ - 1, c0y + r); ++cy)
155 for (int cx = std::max(0, c0x - r); cx <= std::min(nx_ - 1, c0x + r); ++cx) {
156 if (std::max(std::abs(cx - c0x), std::abs(cy - c0y)) != r) continue; // the new ring of cells only
157 for (int ei : cells_[idx(cx, cy)]) {
158 Pt q;
159 const double d = distance_to_edge(edges_[static_cast<std::size_t>(ei)], p, q);
160 if (d < best || (best_edge >= 0 && d == best && ei < best_edge)) best = d, best_edge = ei, out = q;
161 }
162 }
163 if (best_edge >= 0 && best <= static_cast<double>(r) * static_cast<double>(kCell)) break; // nothing in a farther ring can be nearer
164 }
165 return best_edge >= 0;
166 }
167
168 // Reference paths (project rule: every accelerated path has one): the same answers by scanning every edge and vertex. Only the tests call
169 // these.
170 int island_of_linear(Pt p) const {
171 for (const auto& e : edges_)
172 if (on_edge(e, p)) return e.island;
173 bool in = false;
174 double nearest = std::numeric_limits<double>::infinity();
175 int island = -1;
176 for (const auto& e : edges_) {
177 if ((e.a.y > p.y) == (e.b.y > p.y)) continue;
178 const double x = (e.b.x - e.a.x) * (p.y - e.a.y) / (e.b.y - e.a.y) + e.a.x;
179 if (x <= p.x) continue;
180 in = !in;
181 if (x < nearest) nearest = x, island = e.island;
182 }
183 return in ? island : -1;
184 }
185 bool crosses_linear(Pt a, Pt b) const {
186 for (const auto& e : edges_)
187 if (proper_cross(a, b, e.a, e.b)) return true;
188 return false;
189 }
190 bool nearest_boundary_linear(Pt p, double reach, Pt& out) const {
191 double best = reach;
192 bool found = false;
193 for (const auto& e : edges_) {
194 Pt q;
195 if (const double d = distance_to_edge(e, p, q); d < best) best = d, out = q, found = true;
196 }
197 return found;
198 }
199 std::size_t vertices_near_linear(Pt a, Pt b, double bound) const {
200 std::size_t n = 0;
201 for (const auto& v : verts_)
202 if (dist(a, v) + dist(v, b) <= bound) ++n;
203 return n;
204 }
205
206 private:
207 static constexpr Coord kCell = 1'000'000; // grid of edges and vertices
208 static constexpr Coord kRow = 250'000; // rows for the ray of the inside test: finer, a ray meets every edge of its row
209 struct Edge {
210 Pt a, b;
211 int island = 0;
212 };
213 // The distance from p to the edge, and the nearest point of it.
214 static double distance_to_edge(const Edge& e, Pt p, Pt& q) {
215 const double dx = e.b.x - e.a.x, dy = e.b.y - e.a.y, l2 = dx * dx + dy * dy;
216 const double t = l2 > 0 ? std::clamp(((p.x - e.a.x) * dx + (p.y - e.a.y) * dy) / l2, 0.0, 1.0) : 0.0;
217 q = {e.a.x + t * dx, e.a.y + t * dy};
218 return dist(p, q);
219 }
220 static bool on_edge(const Edge& e, Pt p) { // within 3 nm of the edge: the boundary
221 const double dx = e.b.x - e.a.x, dy = e.b.y - e.a.y, l2 = dx * dx + dy * dy;
222 const double t = l2 > 0 ? std::clamp(((p.x - e.a.x) * dx + (p.y - e.a.y) * dy) / l2, 0.0, 1.0) : 0.0;
223 return dist(p, {e.a.x + t * dx, e.a.y + t * dy}) < 3.0;
224 }
225 static double orient(Pt a, Pt b, Pt c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); }
226 // Strictly inside both segments (a touch at an end, or running along one, is not a crossing).
227 static bool proper_cross(Pt a, Pt b, Pt c, Pt d) {
228 const double eps = 1e-3 * std::max(1.0, std::max(dist(a, b), dist(c, d))); // nm^2 scale: 1e-3 of the longer length
229 const double o1 = orient(a, b, c), o2 = orient(a, b, d), o3 = orient(c, d, a), o4 = orient(c, d, b);
230 return ((o1 > eps && o2 < -eps) || (o1 < -eps && o2 > eps)) && ((o3 > eps && o4 < -eps) || (o3 < -eps && o4 > eps));
231 }
232 std::size_t idx(int cx, int cy) const { return static_cast<std::size_t>(cy) * static_cast<std::size_t>(nx_) + static_cast<std::size_t>(cx); }
233 int cell_x(double x) const { return std::clamp(static_cast<int>((x - static_cast<double>(box_.x0)) / static_cast<double>(kCell)), 0, nx_ - 1); }
234 int cell_y(double y) const { return std::clamp(static_cast<int>((y - static_cast<double>(box_.y0)) / static_cast<double>(kCell)), 0, ny_ - 1); }
235 int row_of(double y) const { return std::clamp(static_cast<int>((y - static_cast<double>(box_.y0)) / static_cast<double>(kRow)), 0, nrows_ - 1); }
236
237 // The island of copper the point is on, or -1. A point within 3 nm of an edge is on the boundary (that edge's island). Otherwise a ray to
238 // +x counts the edges it crosses (odd = inside; rings nest properly, so the parity of all rings is "in an outer ring and in no hole"),
239 // and the first edge it meets belongs to the island the point is in.
240 int locate(Pt p) const {
241 if (box_.empty() || p.x < static_cast<double>(box_.x0) - 3 || p.x > static_cast<double>(box_.x1) + 3 || p.y < static_cast<double>(box_.y0) - 3 ||
242 p.y > static_cast<double>(box_.y1) + 3)
243 return -1;
244 const int cx = cell_x(p.x), cy = cell_y(p.y);
245 for (int yy = std::max(0, cy - 1); yy <= std::min(ny_ - 1, cy + 1); ++yy)
246 for (int xx = std::max(0, cx - 1); xx <= std::min(nx_ - 1, cx + 1); ++xx)
247 for (int ei : cells_[idx(xx, yy)])
248 if (on_edge(edges_[static_cast<std::size_t>(ei)], p)) return edges_[static_cast<std::size_t>(ei)].island;
249 bool in = false;
250 double nearest = std::numeric_limits<double>::infinity();
251 int island = -1;
252 for (int ei : rows_[static_cast<std::size_t>(row_of(p.y))]) {
253 const Edge& e = edges_[static_cast<std::size_t>(ei)];
254 if ((e.a.y > p.y) == (e.b.y > p.y)) continue;
255 const double x = (e.b.x - e.a.x) * (p.y - e.a.y) / (e.b.y - e.a.y) + e.a.x;
256 if (x <= p.x) continue;
257 in = !in;
258 if (x < nearest) nearest = x, island = e.island;
259 }
260 return in ? island : -1;
261 }
262
263 std::vector<Edge> edges_;
264 std::vector<Pt> verts_;
265 geom::Box box_;
266 int nx_ = 0, ny_ = 0, nrows_ = 0;
267 std::vector<std::vector<int>> cells_, vcells_, rows_;
268 mutable std::vector<std::uint32_t> stamp_;
269 mutable std::uint32_t clock_ = 0;
270};
271
272
273} // namespace tmk::analysis::detail
bool nearest_boundary_linear(Pt p, double reach, Pt &out) const
Definition region.hpp:190
void vertices_near(Pt a, Pt b, double bound, std::vector< Pt > &out) const
The vertices a path no longer than bound between a and b could bend at: inside the ellipse with foci ...
Definition region.hpp:78
bool contains(Pt p) const
Inside the region (on its boundary counts as inside: a return may run along it).
Definition region.hpp:73
bool crosses(Pt a, Pt b) const
Is the open segment a-b free of properly crossing edges? Touching an edge at a vertex or running alon...
Definition region.hpp:91
int island_of(Pt p) const
Which island of copper (an outer ring with its holes) the point is on, -1 if none: two points on diff...
Definition region.hpp:75
int island_of_linear(Pt p) const
Reference paths (project rule: every accelerated path has one): the same answers by scanning every ed...
Definition region.hpp:170
std::size_t vertices_near_linear(Pt a, Pt b, double bound) const
Definition region.hpp:199
bool crosses_linear(Pt a, Pt b) const
Definition region.hpp:185
Region(const Rings &rings)
Definition region.hpp:33
bool nearest_boundary(Pt p, double reach, Pt &out) const
The point of the region's boundary nearest to p, within reach; false if none.
Definition region.hpp:148
bool sees(Pt a, Pt b) const
The segment lies in the region: no edge crosses it, and every piece of it between two touches of the ...
Definition region.hpp:107
Polygon booleans and offsets on integer nanometres, a thin wrapper over Clipper2 (Angus Johnson,...
Pt to_pt(Point p)
Definition region.hpp:23
double dist(Pt a, Pt b)
Definition region.hpp:25
Point to_point(Pt p)
Definition region.hpp:24
std::vector< Polygon > polygons(const Rings &rs)
The region as separate polygons (a fill's islands), each with its holes.
std::vector< Ring > Rings
a region under the non-zero fill rule: outer rings and holes
Definition clip.hpp:14
std::vector< Point > Ring
closed, first point not repeated
Definition clip.hpp:13
std::int64_t Coord
Engine coordinate: signed 64-bit nanometres.
Definition units.hpp:12
Integer-nanometre points and KiCad-compatible rotation.