osb/source/game/StarParallax.cpp
2023-06-20 14:33:09 +10:00

240 lines
8.3 KiB
C++

#include "StarParallax.hpp"
#include "StarLexicalCast.hpp"
#include "StarJsonExtra.hpp"
#include "StarRoot.hpp"
#include "StarImageMetadataDatabase.hpp"
#include "StarRandom.hpp"
#include "StarAssets.hpp"
#include "StarDataStreamExtra.hpp"
namespace Star {
ParallaxLayer::ParallaxLayer() {
timeOfDayCorrelation = "";
zLevel = 0;
verticalOrigin = 0;
speed = 0;
unlit = false;
lightMapped = false;
fadePercent = 0;
directives = "";
alpha = 1.0f;
}
ParallaxLayer::ParallaxLayer(Json const& store) : ParallaxLayer() {
textures = jsonToStringList(store.get("textures"));
directives = store.getString("directives");
parallaxValue = jsonToVec2F(store.get("parallaxValue"));
repeat = jsonToVec2B(store.get("repeat"));
tileLimitTop = store.optFloat("tileLimitTop");
tileLimitBottom = store.optFloat("tileLimitBottom");
verticalOrigin = store.getFloat("verticalOrigin");
zLevel = store.getFloat("zLevel");
parallaxOffset = jsonToVec2F(store.get("parallaxOffset"));
timeOfDayCorrelation = store.getString("timeOfDayCorrelation");
speed = store.getFloat("speed");
unlit = store.getBool("unlit");
lightMapped = store.getBool("lightMapped");
fadePercent = store.getFloat("fadePercent");
}
Json ParallaxLayer::store() const {
return JsonObject{{"textures", jsonFromStringList(textures)},
{"directives", directives},
{"parallaxValue", jsonFromVec2F(parallaxValue)},
{"repeat", jsonFromVec2B(repeat)},
{"tileLimitTop", jsonFromMaybe(tileLimitTop)},
{"tileLimitBottom", jsonFromMaybe(tileLimitBottom)},
{"verticalOrigin", verticalOrigin},
{"zLevel", zLevel},
{"parallaxOffset", jsonFromVec2F(parallaxOffset)},
{"timeOfDayCorrelation", timeOfDayCorrelation},
{"speed", speed},
{"unlit", unlit},
{"lightMapped", lightMapped},
{"fadePercent", fadePercent}};
}
void ParallaxLayer::addImageDirectives(String const& newDirectives) {
if (!newDirectives.empty())
directives = String::joinWith("?", directives, newDirectives);
}
void ParallaxLayer::fadeToSkyColor(Color skyColor) {
if (fadePercent > 0) {
String fade = "fade=" + skyColor.toHex().slice(0, 6) + "=" + toString(fadePercent);
addImageDirectives(fade);
}
}
DataStream& operator>>(DataStream& ds, ParallaxLayer& parallaxLayer) {
ds.read(parallaxLayer.textures);
ds.read(parallaxLayer.directives);
ds.read(parallaxLayer.alpha);
ds.read(parallaxLayer.parallaxValue);
ds.read(parallaxLayer.repeat);
ds.read(parallaxLayer.tileLimitTop);
ds.read(parallaxLayer.tileLimitBottom);
ds.read(parallaxLayer.verticalOrigin);
ds.read(parallaxLayer.zLevel);
ds.read(parallaxLayer.parallaxOffset);
ds.read(parallaxLayer.timeOfDayCorrelation);
ds.read(parallaxLayer.speed);
ds.read(parallaxLayer.unlit);
ds.read(parallaxLayer.lightMapped);
ds.read(parallaxLayer.fadePercent);
return ds;
}
DataStream& operator<<(DataStream& ds, ParallaxLayer const& parallaxLayer) {
ds.write(parallaxLayer.textures);
ds.write(parallaxLayer.directives);
ds.write(parallaxLayer.alpha);
ds.write(parallaxLayer.parallaxValue);
ds.write(parallaxLayer.repeat);
ds.write(parallaxLayer.tileLimitTop);
ds.write(parallaxLayer.tileLimitBottom);
ds.write(parallaxLayer.verticalOrigin);
ds.write(parallaxLayer.zLevel);
ds.write(parallaxLayer.parallaxOffset);
ds.write(parallaxLayer.timeOfDayCorrelation);
ds.write(parallaxLayer.speed);
ds.write(parallaxLayer.unlit);
ds.write(parallaxLayer.lightMapped);
ds.write(parallaxLayer.fadePercent);
return ds;
}
Parallax::Parallax(String const& assetFile,
uint64_t seed,
float verticalOrigin,
float hueShift,
Maybe<TreeVariant> parallaxTreeVariant) {
m_seed = seed;
m_verticalOrigin = verticalOrigin;
m_parallaxTreeVariant = parallaxTreeVariant;
m_hueShift = hueShift;
m_imageDirectory = "/parallax/images/";
auto assets = Root::singleton().assets();
Json config = assets->json(assetFile);
m_verticalOrigin += config.getFloat("verticalOrigin", 0);
RandomSource rnd(m_seed);
for (auto layerSettings : config.getArray("layers")) {
String kind = layerSettings.getString("kind");
float frequency = layerSettings.getFloat("frequency", 1.0);
if (rnd.randf() > frequency)
continue;
buildLayer(layerSettings, kind);
}
// sort with highest Z level first
stableSort(m_layers, [](ParallaxLayer const& a, ParallaxLayer const& b) { return a.zLevel > b.zLevel; });
}
Parallax::Parallax(Json const& store) {
m_seed = store.getUInt("seed");
m_verticalOrigin = store.getFloat("verticalOrigin");
if (auto treeVariant = store.opt("parallaxTreeVariant"))
m_parallaxTreeVariant = TreeVariant(*treeVariant);
m_hueShift = store.getFloat("hueShift");
m_imageDirectory = store.getString("imageDirectory");
m_layers = store.getArray("layers").transformed(construct<ParallaxLayer>());
stableSort(m_layers, [](ParallaxLayer const& a, ParallaxLayer const& b) { return a.zLevel > b.zLevel; });
}
Json Parallax::store() const {
return JsonObject{{"seed", m_seed},
{"verticalOrigin", m_verticalOrigin},
{"parallaxTreeVariant", m_parallaxTreeVariant ? m_parallaxTreeVariant->toJson() : Json()},
{"hueShift", m_hueShift},
{"imageDirectory", m_imageDirectory},
{"layers", m_layers.transformed(mem_fn(&ParallaxLayer::store))}};
}
void Parallax::fadeToSkyColor(Color const& skyColor) {
for (auto& layer : m_layers) {
layer.fadeToSkyColor(skyColor);
}
}
ParallaxLayers const& Parallax::layers() const {
return m_layers;
}
void Parallax::buildLayer(Json const& layerSettings, String const& kind) {
bool isFoliage = kind.beginsWith("foliage/");
bool isStem = kind.beginsWith("stem/");
String texPath = m_imageDirectory + kind + "/";
if (isFoliage) {
// If our tree type has no foliage, don't construct this layer
if (!m_parallaxTreeVariant || !m_parallaxTreeVariant->foliageSettings.getBool("parallaxFoliage", false))
return;
texPath = m_parallaxTreeVariant->foliageDirectory + "parallax/" + kind.replace("foliage/", "") + "/";
} else if (isStem) {
if (!m_parallaxTreeVariant)
return;
texPath = m_parallaxTreeVariant->stemDirectory + "parallax/" + kind.replace("stem/", "") + "/";
}
ParallaxLayer layer;
RandomSource rnd(m_seed + m_layers.size());
auto imgMetadata = Root::singleton().imageMetadataDatabase();
int baseCount = layerSettings.getInt("baseCount", 1);
int base = rnd.randInt(baseCount - 1) + 1;
layer.textures.append(AssetPath::relativeTo(texPath, "base/" + toString<int>(base) + ".png"));
int modCount = layerSettings.getInt("modCount", 0);
int mod = rnd.randInt(modCount);
if (mod != 0)
layer.textures.append(AssetPath::relativeTo(texPath, "mod/" + toString<int>(mod) + ".png"));
if (layerSettings.get("parallax").type() == Json::Type::Array)
layer.parallaxValue = jsonToVec2F(layerSettings.get("parallax"));
else
layer.parallaxValue = Vec2F::filled(layerSettings.getFloat("parallax"));
bool repeatY = layerSettings.getBool("repeatY", false);
layer.repeat = {true, repeatY};
if (repeatY) {
layer.tileLimitTop = layerSettings.optFloat("tileLimitTop");
layer.tileLimitBottom = layerSettings.optFloat("tileLimitBottom");
}
layer.verticalOrigin = m_verticalOrigin;
layer.zLevel = layer.parallaxValue.sum();
layer.parallaxOffset = {layerSettings.getArray("offset", {0, 0})[0].toFloat(),
layerSettings.getArray("offset", {0, 0})[1].toFloat()}; // shift from bottom left to horizon level in the image
if (!layerSettings.getBool("noRandomOffset", false))
layer.parallaxOffset[0] += rnd.randInt(imgMetadata->imageSize(layer.textures[0])[0]);
layer.timeOfDayCorrelation = layerSettings.getString("timeOfDayCorrelation", "");
layer.speed = rnd.randf(layerSettings.getFloat("minSpeed", 0), layerSettings.getFloat("maxSpeed", 0));
layer.unlit = layerSettings.getBool("unlit", false);
layer.lightMapped = layerSettings.getBool("lightMapped", false);
layer.addImageDirectives(layerSettings.getString("directives", ""));
if (isFoliage)
layer.addImageDirectives(strf("hueshift=%s", m_parallaxTreeVariant->foliageHueShift));
else if (isStem)
layer.addImageDirectives(strf("hueshift=%s", m_parallaxTreeVariant->stemHueShift));
else if (!layerSettings.getBool("nohueshift", false))
layer.addImageDirectives(strf("hueshift=%s", m_hueShift));
layer.fadePercent = layerSettings.getFloat("fadePercent", 0);
m_layers.append(layer);
}
}