载入中...
搜索中...
未找到
HexSphereTopology.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <cstdint>
6#include <unordered_map>
7#include <utility>
8#include <vector>
9
10namespace eve::hexmap {
11namespace {
12
13using Vec = HexVec3;
14
16constexpr float kGoldenRatio = 1.618033988749895f;
17
19[[nodiscard]] constexpr float dot(Vec a, Vec b) noexcept {
20 return a.x * b.x + a.y * b.y + a.z * b.z;
21}
22
24[[nodiscard]] constexpr Vec cross(Vec a, Vec b) noexcept {
25 return Vec{a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
26}
27
29[[nodiscard]] float length(Vec a) noexcept { return std::sqrt(dot(a, a)); }
30
38[[nodiscard]] Vec normalize(Vec a) noexcept {
39 const float len = length(a);
40 if (len <= 1e-20f) return Vec{0.f, 1.f, 0.f};
41 return Vec{a.x / len, a.y / len, a.z / len};
42}
43
45constexpr float kIcosahedronVertices[12][3] = {
46 {-1.f, kGoldenRatio, 0.f}, {1.f, kGoldenRatio, 0.f}, {-1.f, -kGoldenRatio, 0.f}, {1.f, -kGoldenRatio, 0.f},
47 {0.f, -1.f, kGoldenRatio}, {0.f, 1.f, kGoldenRatio}, {0.f, -1.f, -kGoldenRatio}, {0.f, 1.f, -kGoldenRatio},
48 {kGoldenRatio, 0.f, -1.f}, {kGoldenRatio, 0.f, 1.f}, {-kGoldenRatio, 0.f, -1.f}, {-kGoldenRatio, 0.f, 1.f},
49};
50
58constexpr std::int32_t kIcosahedronFaces[20][3] = {
59 {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11}, {1, 5, 9}, {5, 11, 4}, {11, 10, 2},
60 {10, 7, 6}, {7, 1, 8}, {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9}, {4, 9, 5},
61 {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1},
62};
63
65struct Triangle {
66 std::int32_t a = 0;
67 std::int32_t b = 0;
68 std::int32_t c = 0;
69};
70
72[[nodiscard]] std::uint64_t edgeKey(std::int32_t i, std::int32_t j) noexcept {
73 const std::uint32_t low = static_cast<std::uint32_t>(i < j ? i : j);
74 const std::uint32_t high = static_cast<std::uint32_t>(i < j ? j : i);
75 return (static_cast<std::uint64_t>(high) << 32u) | low;
76}
77
79struct EdgeFaces {
80 std::int32_t first = -1;
81 std::int32_t second = -1;
82};
83
85[[nodiscard]] Result<HexSphereTopology> sphereFailure(const char* message) {
88}
89
90} // namespace
91
93 if (subdivision < 0 || subdivision > kMaxHexSphereSubdivision) {
94 return sphereFailure("hex sphere subdivision out of range");
95 }
96
98 topology.subdivision_ = subdivision;
99 topology.frequency_ = std::int32_t{1} << subdivision;
100
101 // --- primal polyhedron --------------------------------------------------
102 std::vector<Vec> vertices;
103 vertices.reserve(12u);
104 for (const auto& vertex : kIcosahedronVertices) vertices.push_back(normalize(Vec{vertex[0], vertex[1], vertex[2]}));
105
106 std::vector<Triangle> triangles;
107 triangles.reserve(20u);
108 for (const auto& face : kIcosahedronFaces) triangles.push_back(Triangle{face[0], face[1], face[2]});
109 for (Triangle& triangle : triangles) {
110 const Vec normal = cross(vertices[triangle.b] - vertices[triangle.a], vertices[triangle.c] - vertices[triangle.a]);
111 const Vec outward = vertices[triangle.a] + vertices[triangle.b] + vertices[triangle.c];
112 if (dot(normal, outward) < 0.f) std::swap(triangle.b, triangle.c);
113 }
114
115 // --- midpoint subdivision, frequency 2^subdivision -----------------------
116 //
117 // Splitting every triangle into four and welding the shared midpoints by edge
118 // key keeps one vertex per physical point, so vertex ids are stable and the
119 // original twelve keep their ids 0..11 -- which is what makes the twelve
120 // pentagons addressable as cells [0, 12).
121 for (std::int32_t level = 0; level < subdivision; ++level) {
122 std::unordered_map<std::uint64_t, std::int32_t> midpoints;
123 midpoints.reserve(vertices.size() * 2u);
124 std::vector<Triangle> refined;
125 refined.reserve(triangles.size() * 4u);
126
127 const auto midpoint = [&](std::int32_t i, std::int32_t j) {
128 const std::uint64_t key = edgeKey(i, j);
129 const auto found = midpoints.find(key);
130 if (found != midpoints.end()) return found->second;
131 const std::int32_t index = static_cast<std::int32_t>(vertices.size());
132 vertices.push_back(normalize(vertices[i] + vertices[j]));
133 midpoints.emplace(key, index);
134 return index;
135 };
136
137 for (const Triangle& triangle : triangles) {
138 const std::int32_t ab = midpoint(triangle.a, triangle.b);
139 const std::int32_t bc = midpoint(triangle.b, triangle.c);
140 const std::int32_t ca = midpoint(triangle.c, triangle.a);
141 refined.push_back(Triangle{triangle.a, ab, ca});
142 refined.push_back(Triangle{ab, triangle.b, bc});
143 refined.push_back(Triangle{ca, bc, triangle.c});
144 refined.push_back(Triangle{ab, bc, ca});
145 }
146 triangles.swap(refined);
147 }
148
149 const std::int32_t cellCount = static_cast<std::int32_t>(vertices.size());
150 topology.directions_ = std::move(vertices);
151
152 // --- dual corners: one per primal face ----------------------------------
153 std::vector<Vec> cornerDirections(triangles.size());
154 for (std::size_t i = 0; i < triangles.size(); ++i) {
155 const Triangle& triangle = triangles[i];
156 cornerDirections[i] =
157 normalize(topology.directions_[triangle.a] + topology.directions_[triangle.b] + topology.directions_[triangle.c]);
158 }
159
160 // --- vertex -> incident face incidence, in CSR form ----------------------
161 const std::int32_t faceCount = static_cast<std::int32_t>(triangles.size());
162 std::vector<std::int32_t> incidenceCount(static_cast<std::size_t>(cellCount), 0);
163 for (const Triangle& triangle : triangles) {
164 ++incidenceCount[triangle.a];
165 ++incidenceCount[triangle.b];
166 ++incidenceCount[triangle.c];
167 }
168 std::vector<std::int32_t> incidenceOffset(static_cast<std::size_t>(cellCount) + 1u, 0);
169 for (std::int32_t cell = 0; cell < cellCount; ++cell) {
170 incidenceOffset[cell + 1] = incidenceOffset[cell] + incidenceCount[cell];
171 }
172 std::vector<std::int32_t> incidence(static_cast<std::size_t>(incidenceOffset[cellCount]), -1);
173 {
174 std::vector<std::int32_t> cursor(incidenceOffset.begin(), incidenceOffset.end() - 1);
175 for (std::int32_t face = 0; face < faceCount; ++face) {
176 const Triangle& triangle = triangles[face];
177 incidence[cursor[triangle.a]++] = face;
178 incidence[cursor[triangle.b]++] = face;
179 incidence[cursor[triangle.c]++] = face;
180 }
181 }
182
183 // --- edge -> its one or two faces ---------------------------------------
184 std::unordered_map<std::uint64_t, EdgeFaces> edgeFaces;
185 edgeFaces.reserve(triangles.size() * 3u);
186 for (std::int32_t face = 0; face < faceCount; ++face) {
187 const Triangle& triangle = triangles[face];
188 const std::int32_t pairs[3][2] = {{triangle.a, triangle.b}, {triangle.b, triangle.c}, {triangle.c, triangle.a}};
189 for (const auto& pair : pairs) {
190 EdgeFaces& entry = edgeFaces[edgeKey(pair[0], pair[1])];
191 if (entry.first < 0) entry.first = face;
192 else if (entry.second < 0) entry.second = face;
193 }
194 }
195
196 const auto slotOf = [&](std::int32_t face, std::int32_t vertex) {
197 const Triangle& triangle = triangles[face];
198 if (triangle.a == vertex) return 0;
199 if (triangle.b == vertex) return 1;
200 return 2;
201 };
202 const auto vertexAt = [&](std::int32_t face, std::int32_t slot) {
203 const Triangle& triangle = triangles[face];
204 if (slot == 0) return triangle.a;
205 if (slot == 1) return triangle.b;
206 return triangle.c;
207 };
208 const auto nextVertexInFace = [&](std::int32_t face, std::int32_t vertex) {
209 return vertexAt(face, (slotOf(face, vertex) + 1) % 3);
210 };
211 const auto otherFaceOfEdge = [&](std::int32_t face, std::int32_t i, std::int32_t j) {
212 const auto found = edgeFaces.find(edgeKey(i, j));
213 if (found == edgeFaces.end()) return -1;
214 if (found->second.first == face) return found->second.second;
215 if (found->second.second == face) return found->second.first;
216 return -1;
217 };
218
219 // --- rotate around every vertex to get its cyclic corner and edge order ---
220 topology.cellOffsets_.assign(static_cast<std::size_t>(cellCount) + 1u, 0);
221 topology.neighbors_.assign(static_cast<std::size_t>(incidenceOffset[cellCount]), 0);
222 topology.corners_.assign(topology.neighbors_.size(), 0);
223
224 std::vector<std::int32_t> orderedFaces(6, -1);
225 for (std::int32_t cell = 0; cell < cellCount; ++cell) {
226 const std::int32_t begin = incidenceOffset[cell];
227 const std::int32_t degree = incidenceOffset[cell + 1] - begin;
228 if (degree < 5 || degree > 6) return sphereFailure("hex sphere cell has an unsupported edge count");
229 topology.cellOffsets_[cell] = begin;
230
231 const std::int32_t firstFace = incidence[begin];
232 std::int32_t current = firstFace;
233 for (std::int32_t direction = 0; direction < degree; ++direction) {
234 orderedFaces[direction] = current;
235 const std::int32_t nextFace = otherFaceOfEdge(current, cell, nextVertexInFace(current, cell));
236 if (nextFace < 0) return sphereFailure("hex sphere half-edge walk left the surface");
237 current = nextFace;
238 }
239 if (current != firstFace) return sphereFailure("hex sphere half-edge walk did not close");
240
241 for (std::int32_t direction = 0; direction < degree; ++direction) {
242 topology.corners_[begin + direction] = orderedFaces[direction];
243 topology.neighbors_[begin + direction] = nextVertexInFace(orderedFaces[direction], cell);
244 }
245 }
246 topology.cellOffsets_[cellCount] = static_cast<std::int32_t>(topology.neighbors_.size());
247 topology.cornerDirections_ = std::move(cornerDirections);
248
249 // --- handedness ---------------------------------------------------------
250 //
251 // Walking "the other face of the edge to the next vertex" is a uniform rule,
252 // so every cell comes out with the same handedness and the only question is
253 // which one. The shared edge of two adjacent cells must expose the same two
254 // corners from both sides, in reversed order; if it does not, reversing every
255 // cell's cyclic order flips the handedness of all of them at once.
256 const auto cornersAgree = [&topology]() {
257 for (std::int32_t cell = 0; cell < topology.cellCount(); ++cell) {
258 const std::int32_t degree = topology.neighborCount(cell);
259 for (std::int32_t direction = 0; direction < degree; ++direction) {
260 const HexSphereCell other = topology.neighbor(cell, direction);
261 const std::int32_t back = topology.directionOf(other, cell);
262 if (back < 0) return false;
263 const std::int32_t otherDegree = topology.neighborCount(other);
264 const std::int32_t here = topology.corners_[topology.cellOffsets_[cell] + direction];
265 const std::int32_t hereNext = topology.corners_[topology.cellOffsets_[cell] + (direction + 1) % degree];
266 const std::int32_t there = topology.corners_[topology.cellOffsets_[other] + back];
267 const std::int32_t thereNext = topology.corners_[topology.cellOffsets_[other] + (back + 1) % otherDegree];
268 if (here != thereNext || hereNext != there) return false;
269 }
270 }
271 return true;
272 };
273
274 if (!cornersAgree()) {
275 for (std::int32_t cell = 0; cell < cellCount; ++cell) {
276 const auto begin = topology.neighbors_.begin() + topology.cellOffsets_[cell];
277 const auto end = topology.neighbors_.begin() + topology.cellOffsets_[cell + 1];
278 const auto cornerBegin = topology.corners_.begin() + topology.cellOffsets_[cell];
279 const auto cornerEnd = topology.corners_.begin() + topology.cellOffsets_[cell + 1];
280 std::reverse(begin, end);
281 std::reverse(cornerBegin, cornerEnd);
282 }
283 if (!cornersAgree()) return sphereFailure("hex sphere winding is inconsistent");
284 }
285
286 // --- Euler characteristic ------------------------------------------------
287 if (topology.cellCount() - topology.edgeCount() + topology.cornerCount() != 2) {
288 return sphereFailure("hex sphere does not close into a sphere");
289 }
290
292}
293
294std::int32_t HexSphereTopology::neighborCount(HexSphereCell cell) const noexcept {
295 if (!contains(cell)) return 0;
296 return cellOffsets_[static_cast<std::size_t>(cell) + 1u] - cellOffsets_[static_cast<std::size_t>(cell)];
297}
298
299HexSphereCell HexSphereTopology::neighbor(HexSphereCell cell, std::int32_t direction) const noexcept {
300 if (direction < 0 || direction >= neighborCount(cell)) return kNoHexSphereCell;
301 return neighbors_[static_cast<std::size_t>(cellOffsets_[static_cast<std::size_t>(cell)] + direction)];
302}
303
304std::int32_t HexSphereTopology::directionOf(HexSphereCell cell, HexSphereCell other) const noexcept {
305 if (!contains(cell) || !contains(other)) return kNoHexSphereCell;
306 const std::int32_t begin = cellOffsets_[static_cast<std::size_t>(cell)];
307 const std::int32_t degree = neighborCount(cell);
308 for (std::int32_t direction = 0; direction < degree; ++direction) {
309 if (neighbors_[static_cast<std::size_t>(begin + direction)] == other) return direction;
310 }
311 return kNoHexSphereCell;
312}
313
314std::int32_t HexSphereTopology::oppositeDirection(HexSphereCell cell, std::int32_t direction) const noexcept {
315 const HexSphereCell other = neighbor(cell, direction);
316 if (other == kNoHexSphereCell) return kNoHexSphereCell;
317 return directionOf(other, cell);
318}
319
320HexVec3 HexSphereTopology::direction(HexSphereCell cell) const noexcept {
321 if (!contains(cell)) return HexVec3{0.f, 1.f, 0.f};
322 return directions_[static_cast<std::size_t>(cell)];
323}
324
325std::int32_t HexSphereTopology::cornerCountOf(HexSphereCell cell) const noexcept { return neighborCount(cell); }
326
327HexVec3 HexSphereTopology::corner(HexSphereCell cell, std::int32_t cornerIndex) const noexcept {
328 if (cornerIndex < 0 || cornerIndex >= neighborCount(cell)) return HexVec3{0.f, 1.f, 0.f};
329 const std::int32_t id = corners_[static_cast<std::size_t>(cellOffsets_[static_cast<std::size_t>(cell)] + cornerIndex)];
330 if (id < 0 || id >= cornerCount()) return HexVec3{0.f, 1.f, 0.f};
331 return cornerDirections_[static_cast<std::size_t>(id)];
332}
333
334HexSphereCell HexSphereTopology::cellAt(HexVec3 unitDirection) const noexcept {
335 if (empty()) return kNoHexSphereCell;
336 const Vec target = normalize(unitDirection);
337
339 float best = dot(directions_[0], target);
340 const std::int32_t seeds = cellCount() < 12 ? cellCount() : 12;
341 for (std::int32_t cell = 1; cell < seeds; ++cell) {
342 const float score = dot(directions_[static_cast<std::size_t>(cell)], target);
343 if (score > best) {
344 best = score;
345 current = cell;
346 }
347 }
348
349 for (;;) {
350 bool improved = false;
351 const std::int32_t degree = neighborCount(current);
352 for (std::int32_t direction = 0; direction < degree; ++direction) {
353 const HexSphereCell candidate =
354 neighbors_[static_cast<std::size_t>(cellOffsets_[static_cast<std::size_t>(current)] + direction)];
355 const float score = dot(directions_[static_cast<std::size_t>(candidate)], target);
356 if (score > best + 1e-7f) {
357 best = score;
358 current = candidate;
359 improved = true;
360 }
361 }
362 if (!improved) break;
363 }
364 return current;
365}
366
367float HexSphereTopology::angularDistance(HexVec3 a, HexVec3 b) noexcept {
368 const float cosine = dot(normalize(a), normalize(b));
369 return std::acos(std::clamp(cosine, -1.f, 1.f));
370}
371
372} // namespace eve::hexmap
LogicalId target
double score
Definition Agent.cpp:50
float length
Definition CaveMesh.cpp:94
int triangle
std::string message
std::uint32_t key
std::uint32_t ab
std::int32_t second
std::int32_t c
std::int32_t first
Icosahedral hex topology: twelve pentagons and hexagons on a sphere.
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< TriangleRef > triangles
Texture * normal
int level
Topology topology
std::vector< Point > vertices
std::string id
Definition PlayHost.cpp:108
float begin
RoadLaneDirection direction
bool found
double current
Cell cell
std::map< Cell, int > best
std::size_t cursor
uint32_t index
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Icosahedral (Goldberg) hex topology for a spherical hex map.
std::int32_t subdivision() const noexcept
The subdivision level this topology was built at.
static Result< HexSphereTopology > build(std::int32_t subdivision)
Builds the topology of one subdivision level.
constexpr std::int32_t kMaxHexSphereSubdivision
Largest supported subdivision level; 10 * 4^7 + 2 = 163842 cells.
constexpr HexSphereCell kNoHexSphereCell
Returned by a spherical cell query that has no answer.
std::int32_t HexSphereCell
Dense identifier of one cell of a spherical hex topology.
Vec2 normalize(const Vec2 &a)
Normalize.
Definition UrbanTypes.h:44
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
Minimal 3-component float vector used by the hex mesh builders.
Definition HexMetrics.h:18