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

289 lines
13 KiB
C++

#include "StarCelestialGraphics.hpp"
#include "StarJsonExtra.hpp"
#include "StarLexicalCast.hpp"
#include "StarFormat.hpp"
#include "StarImageProcessing.hpp"
#include "StarCelestialDatabase.hpp"
#include "StarParallax.hpp"
#include "StarRoot.hpp"
#include "StarBiomeDatabase.hpp"
#include "StarTerrainDatabase.hpp"
#include "StarLiquidsDatabase.hpp"
#include "StarAssets.hpp"
namespace Star {
List<pair<String, float>> CelestialGraphics::drawSystemPlanetaryObject(CelestialParameters const& parameters) {
return {{parameters.getParameter("smallImage").toString(), parameters.getParameter("smallImageScale").toFloat()}};
}
List<pair<String, float>> CelestialGraphics::drawSystemCentralBody(CelestialParameters const& parameters) {
return {{parameters.getParameter("image").toString(), parameters.getParameter("imageScale").toFloat()}};
}
List<pair<String, float>> CelestialGraphics::drawWorld(
CelestialParameters const& celestialParameters, Maybe<CelestialParameters> const& overrideShadowParameters) {
auto& root = Root::singleton();
auto assets = root.assets();
auto liquidsDatabase = root.liquidsDatabase();
CelestialParameters shadowParameters = overrideShadowParameters.value(celestialParameters);
String type = celestialParameters.getParameter("worldType").toString();
List<pair<String, float>> layers;
if (type == "Terrestrial") {
auto terrestrialParameters = as<TerrestrialWorldParameters>(celestialParameters.visitableParameters());
if (!terrestrialParameters)
return {};
auto gfxConfig = jsonMerge(assets->json("/celestial.config:terrestrialGraphics").get("default"),
assets->json("/celestial.config:terrestrialGraphics").get(terrestrialParameters->typeName, JsonObject()));
auto liquidImages = gfxConfig.getString("liquidImages", "");
auto baseImages = gfxConfig.getString("baseImages", "");
auto shadowImages = gfxConfig.getString("shadowImages", "");
auto baseCount = gfxConfig.getInt("baseCount", 0);
auto dynamicsImages = gfxConfig.getString("dynamicsImages", "");
float imageScale = celestialParameters.getParameter("imageScale", 1.0f).toFloat();
// If the planet has water, then draw the corresponding water image as the
// base layer, otherwise use the bottom most mask image.
if (terrestrialParameters->primarySurfaceLiquid != EmptyLiquidId && !liquidImages.empty()) {
String liquidBaseImage = liquidImages.replace("<liquid>", liquidsDatabase->liquidName(terrestrialParameters->primarySurfaceLiquid));
layers.append({move(liquidBaseImage), imageScale});
} else {
if (baseCount > 0) {
String baseLayer = strf("%s?hueshift=%s", baseImages.replace("<biome>",
terrestrialParameters->primaryBiome).replace("<num>", toString(baseCount)), terrestrialParameters->hueShift);
layers.append({move(baseLayer), imageScale});
}
}
// Then draw all the biome layers
for (int i = 0; i < baseCount; ++i) {
String baseImage = baseImages.replace("<num>", toString(baseCount - i));
String hueShiftString, dynamicMaskString;
if (!dynamicsImages.empty())
dynamicMaskString = "?addmask=" + dynamicsImages.replace("<num>", toString(celestialParameters.randomizeParameterRange(gfxConfig.getArray("dynamicsRange"), i).toInt()));
if (terrestrialParameters->hueShift != 0)
hueShiftString = strf("?hueshift=%s", terrestrialParameters->hueShift);
String layer = baseImage + hueShiftString + dynamicMaskString;
layers.append({move(layer), imageScale});
}
if (!shadowImages.empty()) {
String shadow = shadowImages.replace("<num>", toString(shadowParameters.randomizeParameterRange(gfxConfig.getArray("shadowNumber")).toInt()));
layers.append({move(shadow), imageScale});
}
} else if (type == "Asteroids") {
String maskImages = celestialParameters.getParameter("maskImages").toString();
int maskCount = celestialParameters.getParameter("masks").toInt();
String dynamicsImages = celestialParameters.getParameter("dynamicsImages").toString();
float imageScale = celestialParameters.getParameter("imageScale", 1.0f).toFloat();
for (int i = 0; i < maskCount; ++i) {
String biomeMaskBase = maskImages.replace("<num>", toString(maskCount - i));
String dynamicMask = dynamicsImages.replace("<num>", toString(celestialParameters.randomizeParameterRange("dynamicsRange", i).toInt()));
String layer = strf("%s?addmask=%s", biomeMaskBase, dynamicMask);
layers.append({move(layer), imageScale});
}
} else if (type == "FloatingDungeon") {
String image = celestialParameters.getParameter("image").toString();
float imageScale = celestialParameters.getParameter("imageScale", 1.0f).toFloat();
layers.append({move(image), imageScale});
if (!celestialParameters.getParameter("dynamicsImages").toString().empty()) {
String dynamicsImages = celestialParameters.getParameter("dynamicsImages", "").toString();
String dynamicsImage = dynamicsImages.replace("<num>", toString(celestialParameters.randomizeParameterRange("dynamicsRange").toInt()));
layers.append({move(dynamicsImage), imageScale});
}
} else if (type == "GasGiant") {
auto gfxConfig = assets->json("/celestial.config:gasGiantGraphics");
auto baseImage = gfxConfig.getString("baseImage", "");
auto shadowImages = gfxConfig.getString("shadowImages", "");
auto dynamicsImages = gfxConfig.getString("dynamicsImages", "");
auto overlayImages = gfxConfig.getString("overlayImages", "");
auto overlayCount = gfxConfig.getInt("overlayCount", 0);
float imageScale = celestialParameters.getParameter("imageScale", 1.0f).toFloat();
float hueShift = celestialParameters.randomizeParameterRange(gfxConfig.getArray("primaryHueShiftRange")).toFloat();
if (!baseImage.empty())
layers.append({strf("%s?hueshift=%s", baseImage, hueShift), imageScale});
if (!overlayImages.empty()) {
for (int i = 0; i < overlayCount; ++i) {
hueShift += celestialParameters.randomizeParameterRange(gfxConfig.getArray("hueShiftOffsetRange")).toFloat();
String maskImage = dynamicsImages.replace("<num>", toString(celestialParameters.randomizeParameterRange(gfxConfig.getArray("dynamicsRange"), i).toInt()));
String overlayImage = overlayImages.replace("<num>", toString(i));
layers.append({strf("%s?hueshift=%s?addmask=%s", overlayImage, hueShift, maskImage), imageScale});
}
}
if (!shadowImages.empty()) {
String shadow = shadowImages.replace("<num>", toString(shadowParameters.randomizeParameterRange(gfxConfig.getArray("shadowNumber")).toInt()));
layers.append({move(shadow), imageScale});
}
}
return layers;
}
List<pair<String, String>> CelestialGraphics::worldHorizonImages(CelestialParameters const& celestialParameters) {
auto& root = Root::singleton();
auto assets = root.assets();
auto liquidsDatabase = root.liquidsDatabase();
auto getLR = [](String const& base) -> pair<String, String> {
return pair<String, String>(base.replace("<selector>", "l"), base.replace("<selector>", "r"));
};
String type = celestialParameters.getParameter("worldType").toString();
List<pair<String, String>> res;
if (type == "Terrestrial") {
auto terrestrialParameters = as<TerrestrialWorldParameters>(celestialParameters.visitableParameters());
if (!terrestrialParameters)
return {};
auto gfxConfig = jsonMerge(assets->json("/celestial.config:terrestrialHorizonGraphics").get("default"),
assets->json("/celestial.config:terrestrialHorizonGraphics").get(terrestrialParameters->typeName, JsonObject()));
String baseImages = gfxConfig.getString("baseImages");
String atmoTextures = gfxConfig.getString("atmosphereTextures");
String shadowTextures = gfxConfig.getString("shadowTextures");
String maskTextures = gfxConfig.getString("maskTextures");
String liquidTextures = gfxConfig.getString("liquidTextures");
auto numMasks = jsonToVec2I(gfxConfig.get("maskRange"));
auto maskPerPlanetRange = jsonToVec2I(gfxConfig.get("maskPerPlanetRange"));
auto biomeHueShift = "?" + imageOperationToString(HueShiftImageOperation::hueShiftDegrees(terrestrialParameters->hueShift));
if (terrestrialParameters->primarySurfaceLiquid != EmptyLiquidId) {
auto seed = celestialParameters.seed();
RandomSource rand(seed);
int numPlanetMasks = rand.randInt(maskPerPlanetRange[0], maskPerPlanetRange[1]);
List<int> masks;
for (int i = 0; i < numPlanetMasks; ++i)
masks.append(rand.randInt(numMasks[0], numMasks[1]));
String liquidBase = liquidTextures.replace("<liquid>", liquidsDatabase->liquidName(terrestrialParameters->primarySurfaceLiquid));
res.append(getLR(liquidBase));
StringList planetMaskListL;
StringList planetMaskListR;
for (auto m : masks) {
String base = maskTextures.replace("<mask>", toString(m));
auto lr = getLR(base);
planetMaskListL.append(lr.first);
planetMaskListR.append(lr.second);
}
String leftMask, rightMask;
if (!planetMaskListL.empty())
leftMask = "?" + imageOperationToString(AlphaMaskImageOperation{AlphaMaskImageOperation::Additive, planetMaskListL, {0, 0}});
if (!planetMaskListR.empty())
rightMask = "?" + imageOperationToString(AlphaMaskImageOperation{AlphaMaskImageOperation::Additive, planetMaskListR, {0, 0}});
auto toAppend = getLR(baseImages + biomeHueShift);
res.append({toAppend.first + leftMask, toAppend.second + rightMask});
} else {
res.append(getLR(baseImages + biomeHueShift));
}
if (celestialParameters.getParameter("atmosphere", true).toBool())
res.append(getLR(atmoTextures));
res.append(getLR(shadowTextures));
} else if (type == "Asteroids") {
res.append(getLR(assets->json("/celestial.config:asteroidsHorizons").toString()));
} else if (type == "FloatingDungeon") {
auto dungeonParameters = as<FloatingDungeonWorldParameters>(celestialParameters.visitableParameters());
auto dungeonHorizons = assets->json("/celestial.config:floatingDungeonHorizons");
if (dungeonHorizons.contains(dungeonParameters->primaryDungeon))
res.append(getLR(dungeonHorizons.get(dungeonParameters->primaryDungeon).toString()));
}
return res;
}
int CelestialGraphics::worldRadialPosition(CelestialParameters const& parameters) {
if (parameters.coordinate().isPlanetaryBody())
return staticRandomU32(parameters.seed(), "RadialNumber") % planetRadialPositions();
if (parameters.coordinate().isSatelliteBody())
return staticRandomU32(parameters.seed(), "RadialNumber") % satelliteRadialPositions();
return 0;
}
int CelestialGraphics::planetRadialPositions() {
return Root::singleton().assets()->json("/celestial.config:planetRadialSlots").toInt();
}
int CelestialGraphics::satelliteRadialPositions() {
return Root::singleton().assets()->json("/celestial.config:satelliteRadialSlots").toInt();
}
List<pair<String, float>> CelestialGraphics::drawSystemTwinkle(CelestialDatabasePtr celestialDatabase, CelestialCoordinate const& system, double time) {
auto parameters = celestialDatabase->parameters(system);
if (!parameters)
return {};
auto assets = Root::singleton().assets();
int twinkleFrameCount = assets->json("/celestial.config:twinkleFrames").toInt();
float twinkleScale = assets->json("/celestial.config:twinkleScale").toFloat();
String twinkleFrameset = parameters->getParameter("twinkleFrames").toString();
float twinkleTime = parameters->randomizeParameterRange("twinkleTime").toFloat();
String twinkleBackground = parameters->getParameter("twinkleBackground").toString();
String twinkleFrame = strf("%s:%s", twinkleFrameset, (int)(std::fmod<double>(time / twinkleTime, 1.0f) * twinkleFrameCount));
return {{move(twinkleBackground), 1.0f}, {move(twinkleFrame), twinkleScale}};
}
List<pair<String, float>> CelestialGraphics::drawSystemPlanetaryObject(CelestialDatabasePtr celestialDatabase, CelestialCoordinate const& coordinate) {
if (auto params = celestialDatabase->parameters(coordinate))
return drawSystemPlanetaryObject(params.take());
return {};
}
List<pair<String, float>> CelestialGraphics::drawSystemCentralBody(CelestialDatabasePtr celestialDatabase, CelestialCoordinate const& coordinate) {
if (auto params = celestialDatabase->parameters(coordinate))
return drawSystemCentralBody(params.take());
return {};
}
List<pair<String, float>> CelestialGraphics::drawWorld(CelestialDatabasePtr celestialDatabase, CelestialCoordinate const& coordinate) {
auto params = celestialDatabase->parameters(coordinate);
if (!params)
return {};
if (coordinate.isSatelliteBody())
return drawWorld(params.take(), celestialDatabase->parameters(coordinate.parent()));
else
return drawWorld(params.take());
}
List<pair<String, String>> CelestialGraphics::worldHorizonImages(CelestialDatabasePtr celestialDatabase, CelestialCoordinate const& coordinate) {
if (auto params = celestialDatabase->parameters(coordinate))
return worldHorizonImages(params.take());
return {};
}
int CelestialGraphics::worldRadialPosition(CelestialDatabasePtr celestialDatabase, CelestialCoordinate const& coordinate) {
if (auto params = celestialDatabase->parameters(coordinate))
return worldRadialPosition(params.take());
return 0;
}
}