planetsim/src/render/Map2D.cpp
Jonas Reith acc0e5eec9 Initial commit: fanworgen planet sim
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>
2026-06-23 15:08:25 +02:00

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();
}