6#include <unordered_map>
16constexpr float kGoldenRatio = 1.618033988749895f;
19[[nodiscard]]
constexpr float dot(Vec
a, Vec
b)
noexcept {
20 return a.x *
b.x +
a.y *
b.y +
a.z *
b.z;
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};
29[[nodiscard]]
float length(Vec
a)
noexcept {
return std::sqrt(
dot(
a,
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};
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},
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},
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;
85[[nodiscard]] Result<HexSphereTopology> sphereFailure(
const char*
message) {
94 return sphereFailure(
"hex sphere subdivision out of range");
104 for (
const auto& vertex : kIcosahedronVertices)
vertices.push_back(normalize(Vec{vertex[0], vertex[1], vertex[2]}));
108 for (
const auto& face : kIcosahedronFaces)
triangles.push_back(Triangle{face[0], face[1], face[2]});
122 std::unordered_map<std::uint64_t, std::int32_t> midpoints;
123 midpoints.reserve(
vertices.size() * 2u);
124 std::vector<Triangle> refined;
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());
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});
149 const std::int32_t cellCount =
static_cast<std::int32_t
>(
vertices.size());
153 std::vector<Vec> cornerDirections(
triangles.size());
154 for (std::size_t i = 0; i <
triangles.size(); ++i) {
156 cornerDirections[i] =
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);
168 std::vector<std::int32_t> incidenceOffset(
static_cast<std::size_t
>(cellCount) + 1u, 0);
170 incidenceOffset[
cell + 1] = incidenceOffset[
cell] + incidenceCount[
cell];
172 std::vector<std::int32_t> incidence(
static_cast<std::size_t
>(incidenceOffset[cellCount]), -1);
174 std::vector<std::int32_t>
cursor(incidenceOffset.begin(), incidenceOffset.end() - 1);
175 for (std::int32_t face = 0; face < faceCount; ++face) {
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) {
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;
196 const auto slotOf = [&](std::int32_t face, std::int32_t vertex) {
202 const auto vertexAt = [&](std::int32_t face, std::int32_t slot) {
208 const auto nextVertexInFace = [&](std::int32_t face, std::int32_t vertex) {
209 return vertexAt(face, (slotOf(face, vertex) + 1) % 3);
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;
220 topology.cellOffsets_.assign(
static_cast<std::size_t
>(cellCount) + 1u, 0);
221 topology.neighbors_.assign(
static_cast<std::size_t
>(incidenceOffset[cellCount]), 0);
224 std::vector<std::int32_t> orderedFaces(6, -1);
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");
231 const std::int32_t firstFace = incidence[
begin];
232 std::int32_t
current = firstFace;
236 if (nextFace < 0)
return sphereFailure(
"hex sphere half-edge walk left the surface");
239 if (
current != firstFace)
return sphereFailure(
"hex sphere half-edge walk did not close");
246 topology.cellOffsets_[cellCount] =
static_cast<std::int32_t
>(
topology.neighbors_.size());
247 topology.cornerDirections_ = std::move(cornerDirections);
256 const auto cornersAgree = [&
topology]() {
258 const std::int32_t degree =
topology.neighborCount(
cell);
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);
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;
274 if (!cornersAgree()) {
281 std::reverse(cornerBegin, cornerEnd);
283 if (!cornersAgree())
return sphereFailure(
"hex sphere winding is inconsistent");
288 return sphereFailure(
"hex sphere does not close into a sphere");
295 if (!contains(
cell))
return 0;
296 return cellOffsets_[
static_cast<std::size_t
>(
cell) + 1u] - cellOffsets_[
static_cast<std::size_t
>(
cell)];
301 return neighbors_[
static_cast<std::size_t
>(cellOffsets_[
static_cast<std::size_t
>(
cell)] +
direction)];
306 const std::int32_t
begin = cellOffsets_[
static_cast<std::size_t
>(
cell)];
307 const std::int32_t degree = neighborCount(
cell);
317 return directionOf(other,
cell);
321 if (!contains(
cell))
return HexVec3{0.f, 1.f, 0.f};
322 return directions_[
static_cast<std::size_t
>(
cell)];
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)];
336 const Vec
target = normalize(unitDirection);
340 const std::int32_t seeds = cellCount() < 12 ? cellCount() : 12;
342 const float score = dot(directions_[
static_cast<std::size_t
>(
cell)],
target);
350 bool improved =
false;
351 const std::int32_t degree = neighborCount(
current);
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);
362 if (!improved)
break;
368 const float cosine = dot(normalize(
a), normalize(
b));
369 return std::acos(std::clamp(cosine, -1.f, 1.f));
Icosahedral hex topology: twelve pentagons and hexagons on a sphere.
std::vector< Point > vertices
RoadLaneDirection direction
std::map< Cell, int > best
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.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
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.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Minimal 3-component float vector used by the hex mesh builders.