2023-06-20 04:33:09 +00:00
|
|
|
#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;
|
2023-10-11 17:42:24 +00:00
|
|
|
speed = { 0, 0 };
|
2023-06-20 04:33:09 +00:00
|
|
|
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");
|
2023-10-11 17:42:24 +00:00
|
|
|
speed = { store.getFloat("speed"), store.getFloat("speedY", 0.0f) };
|
2023-06-20 04:33:09 +00:00
|
|
|
unlit = store.getBool("unlit");
|
|
|
|
lightMapped = store.getBool("lightMapped");
|
|
|
|
fadePercent = store.getFloat("fadePercent");
|
|
|
|
}
|
|
|
|
|
|
|
|
Json ParallaxLayer::store() const {
|
2023-06-25 06:10:57 +00:00
|
|
|
return JsonObject{
|
|
|
|
{"textures", jsonFromStringList(textures)},
|
2023-06-25 15:42:18 +00:00
|
|
|
{"directives", directives.string()},
|
2023-06-20 04:33:09 +00:00
|
|
|
{"parallaxValue", jsonFromVec2F(parallaxValue)},
|
|
|
|
{"repeat", jsonFromVec2B(repeat)},
|
|
|
|
{"tileLimitTop", jsonFromMaybe(tileLimitTop)},
|
|
|
|
{"tileLimitBottom", jsonFromMaybe(tileLimitBottom)},
|
|
|
|
{"verticalOrigin", verticalOrigin},
|
|
|
|
{"zLevel", zLevel},
|
|
|
|
{"parallaxOffset", jsonFromVec2F(parallaxOffset)},
|
|
|
|
{"timeOfDayCorrelation", timeOfDayCorrelation},
|
2023-10-11 17:42:24 +00:00
|
|
|
{"speed", speed[0]},
|
|
|
|
{"speedY", speed[1]},
|
2023-06-20 04:33:09 +00:00
|
|
|
{"unlit", unlit},
|
|
|
|
{"lightMapped", lightMapped},
|
|
|
|
{"fadePercent", fadePercent}};
|
|
|
|
}
|
|
|
|
|
2023-06-25 06:10:57 +00:00
|
|
|
void ParallaxLayer::addImageDirectives(Directives const& newDirectives) {
|
|
|
|
if (newDirectives) { // TODO: Move to Directives +=
|
|
|
|
if (directives) {
|
2023-11-25 12:03:46 +00:00
|
|
|
String dirString = directives.string();
|
2023-06-25 06:10:57 +00:00
|
|
|
|
2023-11-25 12:03:46 +00:00
|
|
|
auto& newString = newDirectives.shared->string;
|
|
|
|
if (!newString.empty()) {
|
|
|
|
if (newString.utf8().front() != '?')
|
|
|
|
dirString += "?";
|
|
|
|
dirString += newString;
|
2023-06-25 15:42:18 +00:00
|
|
|
}
|
|
|
|
|
2023-11-25 12:03:46 +00:00
|
|
|
directives = move(dirString);
|
2023-06-25 06:10:57 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
directives = newDirectives;
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2023-10-11 17:42:24 +00:00
|
|
|
auto offset = layerSettings.getArray("offset", { 0, 0 });
|
|
|
|
layer.parallaxOffset = { offset[0].toFloat(), offset[1].toFloat() }; // shift from bottom left to horizon level in the image
|
2023-06-20 04:33:09 +00:00
|
|
|
if (!layerSettings.getBool("noRandomOffset", false))
|
|
|
|
layer.parallaxOffset[0] += rnd.randInt(imgMetadata->imageSize(layer.textures[0])[0]);
|
|
|
|
layer.timeOfDayCorrelation = layerSettings.getString("timeOfDayCorrelation", "");
|
2023-10-11 17:42:24 +00:00
|
|
|
auto minSpeed = layerSettings.get("minSpeed", 0.0f);
|
|
|
|
auto maxSpeed = layerSettings.get("maxSpeed", 0.0f);
|
|
|
|
if (!minSpeed.isType(Json::Type::Array))
|
|
|
|
minSpeed = JsonArray{ minSpeed, 0.0f };
|
|
|
|
if (!maxSpeed.isType(Json::Type::Array))
|
|
|
|
maxSpeed = JsonArray{ maxSpeed, 0.0f };
|
|
|
|
layer.speed = { rnd.randf(minSpeed.getFloat(0), maxSpeed.getFloat(0)),
|
|
|
|
rnd.randf(minSpeed.getFloat(1), maxSpeed.getFloat(1)) };
|
2023-06-20 04:33:09 +00:00
|
|
|
layer.unlit = layerSettings.getBool("unlit", false);
|
|
|
|
layer.lightMapped = layerSettings.getBool("lightMapped", false);
|
|
|
|
|
|
|
|
layer.addImageDirectives(layerSettings.getString("directives", ""));
|
|
|
|
if (isFoliage)
|
2023-06-27 10:23:44 +00:00
|
|
|
layer.addImageDirectives(String(strf("hueshift={}", m_parallaxTreeVariant->foliageHueShift)));
|
2023-06-20 04:33:09 +00:00
|
|
|
else if (isStem)
|
2023-06-27 10:23:44 +00:00
|
|
|
layer.addImageDirectives(String(strf("hueshift={}", m_parallaxTreeVariant->stemHueShift)));
|
2023-06-20 04:33:09 +00:00
|
|
|
else if (!layerSettings.getBool("nohueshift", false))
|
2023-06-27 10:23:44 +00:00
|
|
|
layer.addImageDirectives(String(strf("hueshift={}", m_hueShift)));
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
layer.fadePercent = layerSettings.getFloat("fadePercent", 0);
|
|
|
|
|
|
|
|
m_layers.append(layer);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|