在已合入的平面 hexmap 之上增加球面世界,复用其单元记录(HexCellData / HexValues / HexFlags)、几何常量(HexMetrics)、网格容器(HexMeshData)与地形顶点编码 (HexTerrainVertexCode): - HexSphereTopology:二十面体(Goldberg)拓扑。单元是细分后二十面体的顶点, cellCount = 10f^2 + 2,12 个五边形恒为 [0,12)。方向不是算术:五边形只有 5 条边, 跨边查询一律走 directionOf(cell, other),因为不存在处处成立的 opposite。 - HexSphereMap:球面对应物。逐格脏标记(球面无分块网格),高程为径向, 含拾取、距离与笔刷。 - HexSphereMesh:整球地形与海洋网格。平面版用弦 v1->v5 跨越梯田带,这在平面上安全 只因其 5 个采样点共线;球面采样点在弧上,故按扇形同样拆成 4 段以免 T 型接缝。 - HexSphereGenerator:三维值噪声 fBm、按噪声自身分位数定海平面、生物群系。 - HexMapModule:28 个 hexmap.sphere* 脚本绑定。 - examples/hex-planet:轨道相机 + 两个片元着色器。平面着色器把 vWorldPos.y 当海拔、 N.y 当“上”、细节噪声投影到 XZ、远处加距离雾,这四点在球面上均不成立。 - test/hexmap_sphere.cpp:31 个用例;docs/usr/modules/hexmap.md 补充球面章节。 验证:hexmap 套件 66/66 通过(含 31 个球面用例);示例实机跑通并自捕获 hex-planet.png。网格闭合性由焊接后的欧拉特征 V - E + F = 2 断言,朝向由 “无内向三角形”断言。
hex-planet
The hexmap module's hex terrain, wrapped onto a sphere and viewed from orbit.
This is the spherical counterpart of examples/hex-terrain-3d:
the same module, the same HexCellData / HexValues / HexFlags cell records, the
same HexMetrics geometry constants, the same HexMeshData container and the same
HexTerrainVertexCode vertex encoding. What changes is the surface: an icosahedral
(Goldberg) topology instead of a rectangular chunk grid, and a radial "up" instead of
world Y.
make run/win32-debug GAME=examples/hex-planet # Windows
make run/linux-debug GAME=examples/hex-planet # Linux (headless: see AGENTS.md)
What it looks like
Orbit camera around a generated planet, with a hexagonal tessellation you can read at
the limb and in the faceted coastlines. hex-planet.png in this directory is captured
by the example itself after 90 frames. The default pose is static, so two runs agree to
within the ImGui overlay: measured across two runs, 25 of 1,024,000 pixels differ
(the HUD), and the globe itself is identical.
Controls
| Input | Action |
|---|---|
| LMB drag | orbit / pitch |
| wheel | zoom |
space |
toggle auto-spin |
R |
new seed |
[ ] |
subdivision level down / up |
- = |
land share down / up |
esc |
reset the view |
F5 |
save hex-planet.png |
How it is put together
Everything geometric is C++; the script owns only the camera, the input mapping, the two renderables and the HUD.
| Piece | Where | Role |
|---|---|---|
HexSphereTopology |
src/modules/hexmap |
icosahedral cell graph: ids, neighbours, edge directions, corners |
HexSphereMap |
src/modules/hexmap |
editable sphere: cell records, radial elevation, picking, brushes, dirty cells |
HexSphereMesh |
src/modules/hexmap |
builds the terrain and ocean meshes of the whole sphere |
HexSphereGenerator |
src/modules/hexmap |
continents, sea level, biomes from 3D noise |
| script bindings | HexMapModule |
hexmap.sphere* |
| this directory | — | orbit camera, renderables, HUD, shaders |
The four things that do not survive the change of surface
- Edge count. A cell has six edges, or five if it is one of the twelve pentagons.
Nothing iterates a fixed six directions, and there is no
opposite(direction)offset that works everywhere: the sphere map usesdirectionOf(cell, other)throughout, because a hexagon–pentagon edge has no arithmetic opposite. - Edge ownership. The planar builder relies on the chunk grid to emit each shared boundary exactly once. A sphere has no chunk grid, so the lower cell id owns the edge instead — still a total order over the same set of edges.
- "Vertical" is radial. Positions are a unit direction times a radius, and the terrace interpolation of a slope moves along the arc by the horizontal step and along the radius by the vertical one.
- Shading has no horizon. The planar terrain shader reads
vWorldPos.yas altitude,vNormal.yas up, projects detail noise into XZ and fades into distance fog. On a sphere Y spans the whole planet, XZ collapses at the poles, the normal is the radial direction, and there is no horizon to fade into.hex_planet_terrainkeeps the palette and the BRDF byte-for-byte and replaces only those four.
Notes for anyone editing the shaders
The host applies its own Reinhard-style tonemap to this fragment stage's output — a
debug shader emitting a constant 1.0 arrives in the frame buffer as 224/255, i.e.
y = x / (x + 0.139). Both planet shaders invert that curve
(hostTonemapInverse) so the palette they author is the sRGB that actually ships;
without it every colour is silently compressed twice and cannot be tuned.
The two fragment shaders are compiled to SPIR-V, because runtime GLSL compilation is unavailable on Windows:
glslc -o shaders/hex_planet_terrain.frag.spv shaders/hex_planet_terrain.frag
glslc -o shaders/hex_planet_water.frag.spv shaders/hex_planet_water.frag
Relationship to the other hex examples
| Example | Surface | Recipe / module | Camera |
|---|---|---|---|
hex-terrain-3d |
flat, chunked | hexmap (editable map) |
orbiting a ground map |
hex-planet |
spherical, one mesh | hexmap sphere backend |
orbiting the globe |
hex-terrain |
flat mesh | mesh.hexterrain (procgen) |
ground level |
Known gaps
- The ocean is a plain hexagonal cap per flooded cell: it has no shore shelf, no
refraction and no shoreline foam, and it does not cover beaches at sea level
(
waterLevel == elevationis not flooded, matching the module's own rule). Sea ice is a shading term on that cap, not separate geometry. - Cliffs are radial walls with no overhang or erosion detail.
- The generator has no rivers, erosion or plate tectonics; the planar
HexMapGeneratordid not carry over. - The sphere has no rivers, roads, walls, features, fog-of-war or units, and no save format. The cell flags can be set, but there is no spherical geometry, search or serialization behind them.