planetsim/src/render/Colors.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

124 lines
5.4 KiB
C++

#include "Colors.hpp"
#include <algorithm>
#include <cmath>
Color elevationColor(double e, double seaLevel) {
if (e < seaLevel) {
// Water: deep -> shallow blue.
double t = std::clamp((e + 11000.0) / (seaLevel + 11000.0), 0.0, 1.0);
return Color{ (unsigned char)(10 + 20 * t),
(unsigned char)(30 + 90 * t),
(unsigned char)(80 + 120 * t), 255 };
}
// Land: green -> brown -> white by height.
double t = std::clamp(e / 9000.0, 0.0, 1.0);
if (t < 0.4) { double u = t / 0.4;
return Color{ (unsigned char)(60 + 80 * u), (unsigned char)(140 - 30 * u),
(unsigned char)(50), 255 }; }
if (t < 0.75) { double u = (t - 0.4) / 0.35;
return Color{ (unsigned char)(140 - 30 * u), (unsigned char)(110 - 40 * u),
(unsigned char)(50 + 10 * u), 255 }; }
double u = (t - 0.75) / 0.25;
return Color{ (unsigned char)(110 + 145 * u), (unsigned char)(70 + 185 * u),
(unsigned char)(60 + 195 * u), 255 };
}
Color plateColor(int id) {
float h = std::fmod(id * 0.61803398875f, 1.0f) * 360.0f;
return ColorFromHSV(h, 0.65f, 0.85f);
}
Color ageColor(double age, double maxAge) {
double t = std::clamp(age / std::max(1.0, maxAge), 0.0, 1.0);
return Color{ (unsigned char)(40 + 200 * t), (unsigned char)(40),
(unsigned char)(200 - 160 * t), 255 };
}
Color crustColor(bool oceanic) {
return oceanic ? Color{ 30, 60, 160, 255 } // oceanic: deep blue
: Color{ 160, 130, 70, 255 }; // continental: warm brown
}
Color lakeColor() { return Color{ 40, 200, 210, 255 }; } // bright turquoise (vs ocean blue)
Color biomeColor(Biome b) {
switch (b) {
case Biome::Ocean: return Color{ 20, 60, 120, 255 }; // deep blue
case Biome::Ice: return Color{ 235, 240, 250, 255 }; // white (polar caps / snow)
case Biome::Lake: return Color{ 40, 200, 210, 255 }; // bright turquoise
case Biome::Beach: return Color{ 222, 210, 150, 255 }; // pale sand
case Biome::Wetland: return Color{ 70, 115, 95, 255 }; // dark teal-green (swamp/bayou)
case Biome::Grassland: return Color{ 130, 185, 80, 255 }; // light green
case Biome::Savanna: return Color{ 185, 180, 85, 255 }; // yellow-green
case Biome::Desert: return Color{ 214, 184, 120, 255 }; // tan
case Biome::Forest: return Color{ 40, 110, 50, 255 }; // dark green
case Biome::Taiga: return Color{ 55, 105, 85, 255 }; // blue-green (boreal)
case Biome::Tundra: return Color{ 155, 165, 150, 255 }; // pale grey-green
case Biome::Hills: return Color{ 120, 135, 70, 255 }; // olive
case Biome::Mountains: return Color{ 135, 125, 115, 255 }; // grey-brown
}
return Color{ 255, 0, 255, 255 }; // unreachable; flags an unmapped biome
}
const char* biomeName(Biome b) {
switch (b) {
case Biome::Ocean: return "Ocean";
case Biome::Ice: return "Ice cap";
case Biome::Lake: return "Lake";
case Biome::Beach: return "Beach";
case Biome::Wetland: return "Wetland";
case Biome::Grassland: return "Grassland";
case Biome::Savanna: return "Savanna";
case Biome::Desert: return "Desert";
case Biome::Forest: return "Forest";
case Biome::Taiga: return "Taiga";
case Biome::Tundra: return "Tundra";
case Biome::Hills: return "Hills";
case Biome::Mountains: return "Mountains";
}
return "?";
}
const char* colorModeName(ColorMode m) {
switch (m) {
case ColorMode::Elevation: return "Elevation";
case ColorMode::Plate: return "Plates";
case ColorMode::Age: return "Crust age";
case ColorMode::Crust: return "Crust type";
case ColorMode::Biome: return "Biome";
case ColorMode::Temperature: return "Temperature";
case ColorMode::Precip: return "Precipitation";
}
return "?";
}
// Temperature ramp over ~[-40, 40] C: deep blue -> cyan -> green -> yellow -> red.
Color tempColor(double celsius) {
double t = std::clamp((celsius + 40.0) / 80.0, 0.0, 1.0); // 0 cold .. 1 hot
// 4 segments between 5 control colors.
static const unsigned char key[5][3] = {
{ 30, 40, 130 }, // -40 C deep blue
{ 60, 160, 210 }, // -20 C cyan
{ 90, 190, 90 }, // 0 C green
{ 225, 200, 70 }, // +20 C yellow
{ 210, 70, 50 }, // +40 C red
};
double s = t * 4.0; int k = std::min(3, (int)s); double f = s - k;
auto L = [&](int c){ return (unsigned char)(key[k][c] + (key[k + 1][c] - key[k][c]) * f); };
return Color{ L(0), L(1), L(2), 255 };
}
// Precipitation ramp over normalized [0,1]: tan (dry) -> green -> teal/blue (wet).
Color precipColor(double moist01) {
double t = std::clamp(moist01, 0.0, 1.0);
static const unsigned char key[4][3] = {
{ 205, 180, 120 }, // 0.00 dry tan
{ 170, 185, 90 }, // 0.33 scrub
{ 70, 160, 90 }, // 0.66 green
{ 40, 120, 190 }, // 1.00 wet blue
};
double s = t * 3.0; int k = std::min(2, (int)s); double f = s - k;
auto L = [&](int c){ return (unsigned char)(key[k][c] + (key[k + 1][c] - key[k][c]) * f); };
return Color{ L(0), L(1), L(2), 255 };
}