#include "texture.hpp"
PRNG::result_type PRNG::hash(PRNG::result_type value) {
return value * 1103515245U + 12345U;
}
PRNG::result_type PRNG::get() {
return seed = hash(seed);
}
PRNG::result_type PRNG::last() const {
return seed;
}
PRNG::result_type PRNG::get(result_type max) {
assert(max > 0);
return get() % max;
}
PRNG::result_type PRNG::get(result_type min, result_type max) {
assert(max >= min);
return get(max - min + 1) + min;
}
int PRNG::get(int min, int max) {
// NB: safe cast, as pos max must fit unsigned and returned unsigned must fit int
assert(min >= 0);
assert(max >= 0);
return static_cast<int>(get(static_cast<result_type>(min), static_cast<result_type>(max)));
}
fvalue_t PRNG::getf() {
return static_cast<fvalue_t>(get()) / static_cast<fvalue_t>(max());
}
/** @brief Choose a color theme for the given seed and subsequently pick colors from that palette. */
class Theme {
protected:
// clang-format off
static constexpr std::array<std::array<unsigned, 11>, 14> sequentials = {{
// https://github.com/qrohlf/trianglify/blob/master/src/utils/colorbrewer.js
{0x7f3b08U, 0xb35806U, 0xe08214U, 0xfdb863U, 0xfee0b6U, 0xf7f7f7U, 0xd8daebU, 0xb2abd2U, 0x8073acU, 0x542788U, 0x2d004bU}, // PuOr
{0x543005U, 0x8c510aU, 0xbf812dU, 0xdfc27dU, 0xf6e8c3U, 0xf5f5f5U, 0xc7eae5U, 0x80cdc1U, 0x35978fU, 0x01665eU, 0x003c30U}, // BrBG
{0x40004bU, 0x762a83U, 0x9970abU, 0xc2a5cfU, 0xe7d4e8U, 0xf7f7f7U, 0xd9f0d3U, 0xa6dba0U, 0x5aae61U, 0x1b7837U, 0x00441bU}, // PRGn
{0x8e0152U, 0xc51b7dU, 0xde77aeU, 0xf1b6daU, 0xfde0efU, 0xf7f7f7U, 0xe6f5d0U, 0xb8e186U, 0x7fbc41U, 0x4d9221U, 0x276419U}, // PiYG
{0x67001fU, 0xb2182bU, 0xd6604dU, 0xf4a582U, 0xfddbc7U, 0xf7f7f7U, 0xd1e5f0U, 0x92c5deU, 0x4393c3U, 0x2166acU, 0x053061U}, // RdBu
{0x67001fU, 0xb2182bU, 0xd6604dU, 0xf4a582U, 0xfddbc7U, 0xffffffU, 0xe0e0e0U, 0xbababaU, 0x878787U, 0x4d4d4dU, 0x1a1a1aU}, // RdGy
{0xa50026U, 0xd73027U, 0xf46d43U, 0xfdae61U, 0xfee090U, 0xffffbfU, 0xe0f3f8U, 0xabd9e9U, 0x74add1U, 0x4575b4U, 0x313695U}, // RdYlBu
{0x9e0142U, 0xd53e4fU, 0xf46d43U, 0xfdae61U, 0xfee08bU, 0xffffbfU, 0xe6f598U, 0xabdda4U, 0x66c2a5U, 0x3288bdU, 0x5e4fa2U}, // Spectral
{0xa50026U, 0xd73027U, 0xf46d43U, 0xfdae61U, 0xfee08bU, 0xffffbfU, 0xd9ef8bU, 0xa6d96aU, 0x66bd63U, 0x1a9850U, 0x006837U}, // RdYlGn
// https://github.com/plotly/plotly.py/blob/master/packages/python/plotly/_plotly_utils/colors/sequential.py
{0x440154U, 0x482878U, 0x3e4989U, 0x31688eU, 0x26828eU, 0xffffffU, 0x1f9e89U, 0x35b779U, 0x6ece58U, 0xb5de2bU, 0xfde725U}, // Viridis
{0x00224eU, 0x123570U, 0x3b496cU, 0x575d6dU, 0x707173U, 0xffffffU, 0x8a8678U, 0xa59c74U, 0xc3b369U, 0xe1cc55U, 0xfee838U}, // Cividis
{0x0d0887U, 0x46039fU, 0x7201a8U, 0x9c179eU, 0xbd3786U, 0xffffffU, 0xd8576bU, 0xed7953U, 0xfb9f3aU, 0xfdca26U, 0xf0f921U}, // Plasma
{0x2a23a0U, 0x0f4799U, 0x125f8eU, 0x267489U, 0x358888U, 0x419d85U, 0x51b27cU, 0x6fc66bU, 0xa0d65bU, 0xd4e170U, 0xfdee99U}, // haline
{0x245668U, 0x0f7279U, 0x0d8f81U, 0x39ab7eU, 0xffffffU, 0xffffffU, 0xffffffU, 0x39ab7eU, 0x6ec574U, 0xa9dc67U, 0xedef5dU}, // Aggrnyl
}};
// clang-format on
private:
const size_t seed;
static constexpr vertex_t parse_color(unsigned color) {
return vertex_t{(fvalue_t)(color >> 16) / 255.0F, (fvalue_t)((color >> 8) & 0xffU) / 255.0F,
(fvalue_t)(color & 0xffU) / 255.0F};
}
static vertex_t scale_color(vertex_t color) {
#if 1
return color * (1.0F / std::max(color.r, std::max(color.g, color.b)));
#else
return color + (1.0F - std::max(color.r, std::max(color.g, color.b)));
#endif
}
vertex_t get_color(ssize_t index, fvalue_t saturation = 1.0F) const {
const auto& sequential = sequentials.at(seed % sequentials.size());
assert((size_t)std::abs(index) < sequential.size());
assert(0.0F <= saturation && saturation <= 1.0F);
const size_t off = index >= 0 ? (size_t)index : sequential.size() - (size_t)std::abs(index);
const vertex_t color = parse_color(sequential.at(seed % 2 == 0 ? off : sequential.size() - off - 1));
return (scale_color(color) * saturation) + (ones * (1.0F - saturation));
}
public:
Theme(PRNG::result_type seed) : seed(static_cast<size_t>(seed)) {}
vertex_t bg_color() const {
return vertex_t::setted(0.4F);
}
vertex_t bg_color_lo(int variant, fvalue_t intensity) const {
return get_color(0, intensity);
}
vertex_t bg_color_hi(int variant, fvalue_t intensity) const {
return get_color(-1, intensity);
}
vertex_t light_color(int variant) const {
switch (variant % 4) {
case 0:
return get_color(2);
case 1:
return get_color(3);
case 2:
return get_color(-4);
default:
return get_color(-3);
}
}
};
TextureFactory::TextureFactory(PRNG&& rnd, res_t res, vertex2_t<fcoord_t> viewport)
: seed(rnd.last()), noise(std::move(rnd), {rnd.get(res.x / 2, res.x), rnd.get(res.y / 2, res.y)}, viewport) {}
size_t TextureFactory::size() const {
return textures.size();
}
const Texture* TextureFactory::make(const std::pair<int, int>& variant) {
auto hit = cache.find(variant);
if (hit == cache.end()) {
const Theme theme(seed);
#if USE_TEXTURE >= 2
textures.push_back(std::make_unique<NoiseTexture>(theme.bg_color_lo(variant.second, USE_TEXTURE_SATURATION),
theme.bg_color_hi(variant.second, USE_TEXTURE_SATURATION),
0.15F, variant.first % 2 == 0, noise));
#elif USE_TEXTURE >= 1
textures.push_back(std::make_unique<ColorTexture>(theme.bg_color_hi(variant.second, USE_TEXTURE_SATURATION)));
#else
textures.push_back(std::make_unique<ColorTexture>(theme.bg_color()));
#endif
hit = cache.insert({variant, textures.back().get()}).first;
}
return hit->second;
}
const Texture* TextureFactory::make(const std::pair<int, int>& variant_lo, const std::pair<int, int>& variant_hi,
int component) {
if (variant_lo != variant_hi) {
textures.push_back(std::make_unique<GradientTexture>(make(variant_lo), make(variant_hi), component));
return textures.back().get();
}
return make(variant_lo);
}
LightFactory::LightFactory(PRNG&& rnd, fcoord_t radius) : seed(rnd.last()), radius(radius) {}
fcoord_t LightFactory::get_radius() const {
return radius;
}
const PointLight* LightFactory::make(const std::pair<int, int>& variant) {
auto hit = cache.find(variant);
if (hit == cache.end()) {
hit = cache.insert({variant, make(radius, Theme(seed).light_color(variant.second))}).first;
}
return hit->second;
}
const PointLight* LightFactory::make(fcoord_t rad, const vertex_t& color) {
lights.push_back(std::make_unique<PointLight>(rad, color));
return lights.back().get();
}