C++/raylib semi-realistic fantasy/sci-fi planet generator on a fixed icosphere grid (Eulerian: properties flow over fixed cells). World-creation stages: tectonics, continental drift & erosion, hydrology (rivers/lakes), climate (temperature + orographic precipitation), and biome classification. Engine in src/sim (raylib-free, headless-testable), viewer in src/render. See CLAUDE.md and docs/ (design-notes.md, fauna-flora-plan.md = next step). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
3.1 KiB
C++
70 lines
3.1 KiB
C++
#include "Map2D.hpp"
|
|
#include "rlgl.h"
|
|
#include "Projection.hpp"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
void buildMap2D(const Planet& p, Rectangle r, Map2D& m) {
|
|
double hw = EqualEarth::halfWidth(), hh = EqualEarth::halfHeight();
|
|
size_t n = p.cells.size();
|
|
m.pos.resize(n); m.lon.resize(n); m.lat.resize(n);
|
|
for (size_t i = 0; i < n; ++i) {
|
|
double lon, lat; dirToLonLat(p.cells[i].unit, lon, lat);
|
|
double x, y; EqualEarth::forward(lon, lat, x, y);
|
|
m.pos[i] = Vector2{ (float)(r.x + (x / hw * 0.5 + 0.5) * r.width),
|
|
(float)(r.y + (0.5 - y / hh * 0.5) * r.height) };
|
|
m.lon[i] = lon; m.lat[i] = lat;
|
|
}
|
|
}
|
|
|
|
double wrapPi(double l) { while (l > M_PI) l -= 2*M_PI; while (l < -M_PI) l += 2*M_PI; return l; }
|
|
|
|
Vector2 projLonLat(double lon, double lat, double lonOffset, Rectangle r) {
|
|
double hw = EqualEarth::halfWidth(), hh = EqualEarth::halfHeight();
|
|
double x, y; EqualEarth::forward(wrapPi(lon + lonOffset), lat, x, y);
|
|
return Vector2{ (float)(r.x + (x / hw * 0.5 + 0.5) * r.width),
|
|
(float)(r.y + (0.5 - y / hh * 0.5) * r.height) };
|
|
}
|
|
Vector2 mapScreen(const Map2D& m, int idx, Rectangle r, double lonOffset) {
|
|
return projLonLat(m.lon[idx], m.lat[idx], lonOffset, r);
|
|
}
|
|
|
|
void drawMap2D(const Planet& p, const std::vector<Color>& vc,
|
|
const Map2D& m, Rectangle r, double lonOffset) {
|
|
double hw = EqualEarth::halfWidth();
|
|
auto px = [&](double lon, double lat) -> float {
|
|
double x, y; EqualEarth::forward(lon, lat, x, y);
|
|
return (float)(r.x + (x / hw * 0.5 + 0.5) * r.width);
|
|
};
|
|
const std::vector<int>& tri = p.triIndices();
|
|
rlDisableBackfaceCulling();
|
|
rlBegin(RL_TRIANGLES);
|
|
for (size_t k = 0; k + 2 < tri.size(); k += 3) {
|
|
int v[3] = { tri[k], tri[k + 1], tri[k + 2] };
|
|
double lo[3] = { wrapPi(m.lon[v[0]] + lonOffset), wrapPi(m.lon[v[1]] + lonOffset),
|
|
wrapPi(m.lon[v[2]] + lonOffset) };
|
|
double mn = std::min({lo[0], lo[1], lo[2]});
|
|
double mx = std::max({lo[0], lo[1], lo[2]});
|
|
if (mx - mn <= M_PI) { // fast path (no wrap)
|
|
for (int t = 0; t < 3; ++t) {
|
|
rlColor4ub(vc[v[t]].r, vc[v[t]].g, vc[v[t]].b, 255);
|
|
rlVertex2f(px(lo[t], m.lat[v[t]]), m.pos[v[t]].y);
|
|
}
|
|
} else { // antimeridian seam
|
|
double ul[3] = { lo[0], lo[1], lo[2] }; // unwrap around v0
|
|
double ref = ul[0];
|
|
for (int t = 0; t < 3; ++t) {
|
|
while (ul[t] - ref > M_PI) ul[t] -= 2 * M_PI;
|
|
while (ref - ul[t] > M_PI) ul[t] += 2 * M_PI;
|
|
}
|
|
const double shift[3] = { 0.0, 2 * M_PI, -2 * M_PI }; // both edges; scissor clips
|
|
for (double sh : shift)
|
|
for (int t = 0; t < 3; ++t) {
|
|
rlColor4ub(vc[v[t]].r, vc[v[t]].g, vc[v[t]].b, 255);
|
|
rlVertex2f(px(ul[t] + sh, m.lat[v[t]]), m.pos[v[t]].y);
|
|
}
|
|
}
|
|
}
|
|
rlEnd();
|
|
}
|