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>
124 lines
5.4 KiB
C++
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 };
|
|
}
|