#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, 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"), store.getFloat("speedY", 0.0f) }; unlit = store.getBool("unlit"); lightMapped = store.getBool("lightMapped"); fadePercent = store.getFloat("fadePercent"); } Json ParallaxLayer::store() const { return JsonObject{ {"textures", jsonFromStringList(textures)}, {"directives", directives.string()}, {"parallaxValue", jsonFromVec2F(parallaxValue)}, {"repeat", jsonFromVec2B(repeat)}, {"tileLimitTop", jsonFromMaybe(tileLimitTop)}, {"tileLimitBottom", jsonFromMaybe(tileLimitBottom)}, {"verticalOrigin", verticalOrigin}, {"zLevel", zLevel}, {"parallaxOffset", jsonFromVec2F(parallaxOffset)}, {"timeOfDayCorrelation", timeOfDayCorrelation}, {"speed", speed[0]}, {"speedY", speed[1]}, {"unlit", unlit}, {"lightMapped", lightMapped}, {"fadePercent", fadePercent}}; } void ParallaxLayer::addImageDirectives(Directives const& newDirectives) { if (newDirectives) { // TODO: Move to Directives += if (directives) { String dirString = directives.string(); auto& newString = newDirectives->string; if (!newString.empty()) { if (newString.utf8().front() != '?') dirString += "?"; dirString += newString; } directives = std::move(dirString); } else 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 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()); 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(base) + ".png")); int modCount = layerSettings.getInt("modCount", 0); int mod = rnd.randInt(modCount); if (mod != 0) layer.textures.append(AssetPath::relativeTo(texPath, "mod/" + toString(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(); 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 if (!layerSettings.getBool("noRandomOffset", false)) layer.parallaxOffset[0] += rnd.randInt(imgMetadata->imageSize(layer.textures[0])[0]); layer.timeOfDayCorrelation = layerSettings.getString("timeOfDayCorrelation", ""); 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)) }; layer.unlit = layerSettings.getBool("unlit", false); layer.lightMapped = layerSettings.getBool("lightMapped", false); layer.addImageDirectives(layerSettings.getString("directives", "")); if (isFoliage) layer.addImageDirectives(String(strf("hueshift={}", m_parallaxTreeVariant->foliageHueShift))); else if (isStem) layer.addImageDirectives(String(strf("hueshift={}", m_parallaxTreeVariant->stemHueShift))); else if (!layerSettings.getBool("nohueshift", false)) layer.addImageDirectives(String(strf("hueshift={}", m_hueShift))); layer.fadePercent = layerSettings.getFloat("fadePercent", 0); m_layers.append(layer); } }