osb/source/rendering/StarEnvironmentPainter.cpp
Kai Blaschke 431a9c00a5
Fixed a huge amount of Clang warnings
On Linux and macOS, using Clang to compile OpenStarbound produces about 400 MB worth of warnings during the build, making the compiler output unreadable and slowing the build down considerably.

99% of the warnings were unqualified uses of std::move and std::forward, which are now all properly qualified.

Fixed a few other minor warnings about non-virtual destructors and some uses of std::move preventing copy elision on temporary objects.

Most remaining warnings are now unused parameters.
2024-02-19 16:55:19 +01:00

477 lines
21 KiB
C++

#include "StarEnvironmentPainter.hpp"
#include "StarLexicalCast.hpp"
#include "StarTime.hpp"
#include "StarXXHash.hpp"
#include "StarJsonExtra.hpp"
#include "StarLogging.hpp"
#include "StarMathCommon.hpp"
namespace Star {
float const EnvironmentPainter::SunriseTime = 0.072f;
float const EnvironmentPainter::SunsetTime = 0.42f;
float const EnvironmentPainter::SunFadeRate = 0.07f;
float const EnvironmentPainter::MaxFade = 0.3f;
float const EnvironmentPainter::RayPerlinFrequency = 0.005f; // Arbitrary, part of using the Perlin as a PRNG
float const EnvironmentPainter::RayPerlinAmplitude = 2;
int const EnvironmentPainter::RayCount = 60;
float const EnvironmentPainter::RayMinWidth = 0.8f; // % of its sector
float const EnvironmentPainter::RayWidthVariance = 5.0265f; // % of its sector
float const EnvironmentPainter::RayAngleVariance = 6.2832f; // Radians
float const EnvironmentPainter::SunRadius = 50;
float const EnvironmentPainter::RayColorDependenceLevel = 3.0f;
float const EnvironmentPainter::RayColorDependenceScale = 0.00625f;
float const EnvironmentPainter::RayUnscaledAlphaVariance = 2.0943f;
float const EnvironmentPainter::RayMinUnscaledAlpha = 1;
Vec3B const EnvironmentPainter::RayColor = Vec3B(255, 255, 200);
EnvironmentPainter::EnvironmentPainter(RendererPtr renderer) {
m_renderer = std::move(renderer);
m_textureGroup = make_shared<AssetTextureGroup>(m_renderer->createTextureGroup(TextureGroupSize::Large));
m_timer = 0;
m_rayPerlin = PerlinF(1, RayPerlinFrequency, RayPerlinAmplitude, 0, 2.0f, 2.0f, Random::randu64());
}
void EnvironmentPainter::update(float dt) {
// Allows the rays to change alpha with time.
m_timer += dt;
m_timer = std::fmod(m_timer, Constants::pi * 100000.0);
}
void EnvironmentPainter::renderStars(float pixelRatio, Vec2F const& screenSize, SkyRenderData const& sky) {
if (!sky.settings)
return;
float nightSkyAlpha = 1.0f - min(sky.dayLevel, sky.skyAlpha);
if (nightSkyAlpha <= 0.0f)
return;
Vec4B color(255, 255, 255, 255 * nightSkyAlpha);
Vec2F viewSize = screenSize / pixelRatio;
Vec2F viewCenter = viewSize / 2;
Vec2F viewMin = sky.starOffset - viewCenter;
auto newStarsHash = starsHash(sky, viewSize);
if (newStarsHash != m_starsHash) {
m_starsHash = newStarsHash;
setupStars(sky);
}
float screenBuffer = sky.settings.queryFloat("stars.screenBuffer");
PolyF field = PolyF(RectF::withSize(viewMin, Vec2F(viewSize)).padded(screenBuffer));
field.rotate(-sky.starRotation, Vec2F(sky.starOffset));
Mat3F transform = Mat3F::identity();
transform.translate(-viewMin);
transform.rotate(sky.starRotation, viewCenter);
int starTwinkleMin = sky.settings.queryInt("stars.twinkleMin");
int starTwinkleMax = sky.settings.queryInt("stars.twinkleMax");
size_t starTypesSize = sky.starTypes().size();
auto stars = m_starGenerator->generate(field, [&](RandomSource& rand) {
size_t starType = rand.randu32() % starTypesSize;
float frameOffset = rand.randu32() % sky.starFrames + rand.randf(starTwinkleMin, starTwinkleMax);
return pair<size_t, float>(starType, frameOffset);
});
RectF viewRect = RectF::withSize(Vec2F(), viewSize).padded(screenBuffer);
auto& primitives = m_renderer->immediatePrimitives();
for (auto& star : stars) {
Vec2F screenPos = transform.transformVec2(star.first);
if (viewRect.contains(screenPos)) {
size_t starFrame = (size_t)(sky.epochTime + star.second.second) % sky.starFrames;
auto const& texture = m_starTextures[star.second.first * sky.starFrames + starFrame];
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), texture, screenPos * pixelRatio - Vec2F(texture->size()) / 2, 1.0, color, 0.0f);
}
}
m_renderer->flush();
}
void EnvironmentPainter::renderDebrisFields(float pixelRatio, Vec2F const& screenSize, SkyRenderData const& sky) {
if (!sky.settings)
return;
if (sky.type == SkyType::Orbital || sky.type == SkyType::Warp) {
Vec2F viewSize = screenSize / pixelRatio;
Vec2F viewCenter = viewSize / 2;
Vec2D viewMin = Vec2D(sky.starOffset - viewCenter);
Mat3F rotMatrix = Mat3F::rotation(sky.starRotation, viewCenter);
JsonArray debrisFields = sky.settings.queryArray("spaceDebrisFields");
for (size_t i = 0; i < debrisFields.size(); ++i) {
Json debrisField = debrisFields[i];
Vec2F spaceDebrisVelocityRange = jsonToVec2F(debrisField.query("velocityRange"));
float debrisXVel = staticRandomFloatRange(spaceDebrisVelocityRange[0], spaceDebrisVelocityRange[1], sky.skyParameters.seed, i, "DebrisFieldXVel");
float debrisYVel = staticRandomFloatRange(spaceDebrisVelocityRange[0], spaceDebrisVelocityRange[1], sky.skyParameters.seed, i, "DebrisFieldYVel");
// Translate the entire field to make the debris seem as though they are moving
Vec2D velocityOffset = -Vec2D(debrisXVel, debrisYVel) * sky.epochTime;
JsonArray imageOptions = debrisField.query("list").toArray();
Vec2U biggest = Vec2U();
for (Json const& json : imageOptions) {
TexturePtr texture = m_textureGroup->loadTexture(*json.stringPtr());
biggest = biggest.piecewiseMax(texture->size());
}
float screenBuffer = ceil((float)biggest.max() * (float)Constants::sqrt2);
PolyD field = PolyD(RectD::withSize(viewMin + velocityOffset, Vec2D(viewSize)).padded(screenBuffer));
Vec2F debrisAngularVelocityRange = jsonToVec2F(debrisField.query("angularVelocityRange"));
auto debrisItems = m_debrisGenerators[i]->generate(field,
[&](RandomSource& rand) {
StringView debrisImage = *rand.randFrom(imageOptions).stringPtr();
float debrisAngularVelocity = rand.randf(debrisAngularVelocityRange[0], debrisAngularVelocityRange[1]);
return pair<StringView, float>(debrisImage, debrisAngularVelocity);
});
Vec2D debrisPositionOffset = viewMin + velocityOffset;
for (auto& debrisItem : debrisItems) {
Vec2F debrisPosition = rotMatrix.transformVec2(Vec2F(debrisItem.first - debrisPositionOffset));
float debrisAngle = fmod(Constants::deg2rad * debrisItem.second.second * sky.epochTime, Constants::pi * 2) + sky.starRotation;
drawOrbiter(pixelRatio, screenSize, sky, {SkyOrbiterType::SpaceDebris, 1.0f, debrisAngle, debrisItem.second.first, debrisPosition});
}
}
m_renderer->flush();
}
}
void EnvironmentPainter::renderBackOrbiters(float pixelRatio, Vec2F const& screenSize, SkyRenderData const& sky) {
for (auto const& orbiter : sky.backOrbiters(screenSize / pixelRatio))
drawOrbiter(pixelRatio, screenSize, sky, orbiter);
m_renderer->flush();
}
void EnvironmentPainter::renderPlanetHorizon(float pixelRatio, Vec2F const& screenSize, SkyRenderData const& sky) {
auto planetHorizon = sky.worldHorizon(screenSize / pixelRatio);
if (planetHorizon.empty())
return;
// Can't bail sooner, need to queue all textures
bool allLoaded = true;
for (auto const& layer : planetHorizon.layers) {
if (!m_textureGroup->tryTexture(layer.first) || !m_textureGroup->tryTexture(layer.second))
allLoaded = false;
}
if (!allLoaded)
return;
float planetPixelRatio = pixelRatio * planetHorizon.scale;
Vec2F center = planetHorizon.center * pixelRatio;
auto& primitives = m_renderer->immediatePrimitives();
for (auto const& layer : planetHorizon.layers) {
TexturePtr leftTexture = m_textureGroup->loadTexture(layer.first);
Vec2F leftTextureSize(leftTexture->size());
TexturePtr rightTexture = m_textureGroup->loadTexture(layer.second);
Vec2F rightTextureSize(rightTexture->size());
Vec2F leftLayer = center;
leftLayer[0] -= leftTextureSize[0] * planetPixelRatio;
auto leftRect = RectF::withSize(leftLayer, leftTextureSize * planetPixelRatio);
PolyF leftImage = PolyF(leftRect);
leftImage.rotate(planetHorizon.rotation, center);
auto rightRect = RectF::withSize(center, rightTextureSize * planetPixelRatio);
PolyF rightImage = PolyF(rightRect);
rightImage.rotate(planetHorizon.rotation, center);
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), std::move(leftTexture),
leftImage[0], Vec2F(0, 0),
leftImage[1], Vec2F(leftTextureSize[0], 0),
leftImage[2], Vec2F(leftTextureSize[0], leftTextureSize[1]),
leftImage[3], Vec2F(0, leftTextureSize[1]), Vec4B::filled(255), 0.0f);
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), std::move(rightTexture),
rightImage[0], Vec2F(0, 0),
rightImage[1], Vec2F(rightTextureSize[0], 0),
rightImage[2], Vec2F(rightTextureSize[0], rightTextureSize[1]),
rightImage[3], Vec2F(0, rightTextureSize[1]), Vec4B::filled(255), 0.0f);
}
m_renderer->flush();
}
void EnvironmentPainter::renderFrontOrbiters(float pixelRatio, Vec2F const& screenSize, SkyRenderData const& sky) {
for (auto const& orbiter : sky.frontOrbiters(screenSize / pixelRatio))
drawOrbiter(pixelRatio, screenSize, sky, orbiter);
m_renderer->flush();
}
void EnvironmentPainter::renderSky(Vec2F const& screenSize, SkyRenderData const& sky) {
auto& primitives = m_renderer->immediatePrimitives();
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), TexturePtr(),
RenderVertex{Vec2F(0, 0), Vec2F(), sky.bottomRectColor.toRgba(), 0.0f},
RenderVertex{Vec2F(screenSize[0], 0), Vec2F(), sky.bottomRectColor.toRgba(), 0.0f},
RenderVertex{screenSize, Vec2F(), sky.topRectColor.toRgba(), 0.0f},
RenderVertex{Vec2F(0, screenSize[1]), Vec2F(), sky.topRectColor.toRgba(), 0.0f});
// Flash overlay for Interstellar travel
Vec4B flashColor = sky.flashColor.toRgba();
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), RectF(Vec2F(), screenSize), flashColor, 0.0f);
m_renderer->flush();
}
// TODO: Fix this to work with decimal zoom levels. Currently, the clouds shake rapidly when interpolating between zoom levels.
void EnvironmentPainter::renderParallaxLayers(
Vec2F parallaxWorldPosition, WorldCamera const& camera, ParallaxLayers const& layers, SkyRenderData const& sky) {
// Note: the "parallax space" referenced below is a grid where the scale of each cell is the size of the parallax image
auto& primitives = m_renderer->immediatePrimitives();
for (auto& layer : layers) {
if (layer.alpha == 0)
continue;
Vec4B drawColor;
if (layer.unlit || layer.lightMapped)
drawColor = Vec4B(255, 255, 255, floor(255 * layer.alpha));
else
drawColor = Vec4B(sky.environmentLight.toRgb(), floor(255 * layer.alpha));
Vec2F parallaxValue = layer.parallaxValue;
Vec2B parallaxRepeat = layer.repeat;
Vec2F parallaxOrigin = {0.0f, layer.verticalOrigin};
AssetPath first = layer.textures.first();
first.directives += layer.directives;
Vec2F parallaxSize = Vec2F(m_textureGroup->loadTexture(first)->size());
Vec2F parallaxPixels = parallaxSize * camera.pixelRatio();
// texture offset in *screen pixel space*
Vec2F parallaxOffset = layer.parallaxOffset * camera.pixelRatio();
if (layer.speed[0] != 0) {
double drift = fmod((double)layer.speed[0] * (sky.epochTime / (double)sky.dayLength), (double)parallaxSize[0]);
parallaxOffset[0] = fmod(parallaxOffset[0] + drift * camera.pixelRatio(), parallaxPixels[0]);
}
if (layer.speed[1] != 0) {
double drift = fmod((double)layer.speed[1] * (sky.epochTime / (double)sky.dayLength), (double)parallaxSize[1]);
parallaxOffset[1] = fmod(parallaxOffset[1] + drift * camera.pixelRatio(), parallaxPixels[1]);
}
// parallax camera world position in *parallax space*
Vec2F parallaxCameraCenter = parallaxWorldPosition - parallaxOrigin;
parallaxCameraCenter =
Vec2F((((parallaxCameraCenter[0] / parallaxPixels[0]) * TilePixels) * camera.pixelRatio()) / parallaxValue[0],
(((parallaxCameraCenter[1] / parallaxPixels[1]) * TilePixels) * camera.pixelRatio()) / parallaxValue[1]);
// width / height of screen in *parallax space*
float parallaxScreenWidth = camera.screenSize()[0] / parallaxPixels[0];
float parallaxScreenHeight = camera.screenSize()[1] / parallaxPixels[1];
// screen world position in *parallax space*
float parallaxScreenLeft = parallaxCameraCenter[0] - parallaxScreenWidth / 2.0;
float parallaxScreenBottom = parallaxCameraCenter[1] - parallaxScreenHeight / 2.0;
// screen boundary world positions in *parallax space*
Vec2F parallaxScreenOffset = parallaxOffset.piecewiseDivide(parallaxPixels);
int left = floor(parallaxScreenLeft + parallaxScreenOffset[0]);
int bottom = floor(parallaxScreenBottom + parallaxScreenOffset[1]);
int right = ceil(parallaxScreenLeft + parallaxScreenWidth + parallaxScreenOffset[0]);
int top = ceil(parallaxScreenBottom + parallaxScreenHeight + parallaxScreenOffset[1]);
// positions to start tiling in *screen pixel space*
float pixelLeft = (left - parallaxScreenLeft) * parallaxPixels[0] - parallaxOffset[0];
float pixelBottom = (bottom - parallaxScreenBottom) * parallaxPixels[1] - parallaxOffset[1];
// vertical top and bottom cutoff points in *parallax space*
float tileLimitTop = top;
if (layer.tileLimitTop)
tileLimitTop = (layer.parallaxOffset[1] - layer.tileLimitTop.value()) / parallaxSize[1];
float tileLimitBottom = bottom;
if (layer.tileLimitBottom)
tileLimitBottom = (layer.parallaxOffset[1] - layer.tileLimitBottom.value()) / parallaxSize[1];
float lightMapMultiplier = (!layer.unlit && layer.lightMapped) ? 1.0f : 0.0f;
for (int y = bottom; y <= top; ++y) {
if (!(parallaxRepeat[1] || y == 0) || y > tileLimitTop || y + 1 < tileLimitBottom)
continue;
for (int x = left; x <= right; ++x) {
if (!(parallaxRepeat[0] || x == 0))
continue;
float pixelTileLeft = pixelLeft + (x - left) * parallaxPixels[0];
float pixelTileBottom = pixelBottom + (y - bottom) * parallaxPixels[1];
Vec2F anchorPoint(pixelTileLeft, pixelTileBottom);
RectF subImage = RectF::withSize(Vec2F(), parallaxSize);
if (tileLimitTop != top && y + 1 > tileLimitTop)
subImage.setYMin(parallaxSize[1] * (1.0f - fpart(tileLimitTop)));
if (tileLimitBottom != bottom && y < tileLimitBottom)
anchorPoint[1] += fpart(tileLimitBottom) * parallaxPixels[1];
for (auto const& textureImage : layer.textures) {
AssetPath withDirectives = textureImage;
withDirectives.directives += layer.directives;
if (auto texture = m_textureGroup->tryTexture(withDirectives)) {
RectF drawRect = RectF::withSize(anchorPoint, subImage.size() * camera.pixelRatio());
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), std::move(texture),
RenderVertex{drawRect.min(), subImage.min(), drawColor, lightMapMultiplier},
RenderVertex{{drawRect.xMax(), drawRect.yMin()}, {subImage.xMax(), subImage.yMin()}, drawColor, lightMapMultiplier},
RenderVertex{drawRect.max(), subImage.max(), drawColor, lightMapMultiplier},
RenderVertex{{drawRect.xMin(), drawRect.yMax()}, {subImage.xMin(), subImage.yMax()}, drawColor, lightMapMultiplier});
}
}
}
}
}
m_renderer->flush();
}
void EnvironmentPainter::cleanup(int64_t textureTimeout) {
m_textureGroup->cleanup(textureTimeout);
}
void EnvironmentPainter::drawRays(
float pixelRatio, SkyRenderData const& sky, Vec2F start, float length, double time, float alpha) {
// All magic constants are either 2PI or arbritrary to allow the Perlin to act
// as a PRNG
float sectorWidth = 2 * Constants::pi / RayCount; // Radians
Vec3B color = sky.topRectColor.toRgb();
for (int i = 0; i < RayCount; i++)
drawRay(pixelRatio,
sky,
start,
sectorWidth * (std::abs(m_rayPerlin.get(i * 25)) * RayWidthVariance + RayMinWidth),
length,
i * sectorWidth + m_rayPerlin.get(i * 314) * RayAngleVariance,
time,
color,
alpha);
m_renderer->flush();
}
void EnvironmentPainter::drawRay(float pixelRatio,
SkyRenderData const& sky,
Vec2F start,
float width,
float length,
float angle,
double time,
Vec3B color,
float alpha) {
// All magic constants are arbritrary to allow the Perlin to act as a PRNG
float currentTime = sky.timeOfDay / sky.dayLength;
float timeSinceSunEvent = std::min(std::abs(currentTime - SunriseTime), std::abs(currentTime - SunsetTime));
float percentFaded = MaxFade * (1.0f - std::min(1.0f, std::pow(timeSinceSunEvent / SunFadeRate, 2.0f)));
// Gets the current average sky color
color = (Vec3B)((Vec3F)color * (1 - percentFaded) + (Vec3F)sky.mainSkyColor.toRgb() * percentFaded);
// Sum is used to vary the ray intensity based on sky color
// Rays show up more on darker backgrounds, so this scales to remove that
float sum = std::pow((color[0] + color[1]) * RayColorDependenceScale, RayColorDependenceLevel);
m_renderer->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(), TexturePtr(),
RenderVertex{start + Vec2F(std::cos(angle + width), std::sin(angle + width)) * length, {}, Vec4B(RayColor, 0), 0.0f},
RenderVertex{start + Vec2F(std::cos(angle + width), std::sin(angle + width)) * SunRadius * pixelRatio,
{},
Vec4B(RayColor,
(int)(RayMinUnscaledAlpha + std::abs(m_rayPerlin.get(angle * 896 + time * 30) * RayUnscaledAlphaVariance))
* sum
* alpha), 0.0f},
RenderVertex{start + Vec2F(std::cos(angle), std::sin(angle)) * SunRadius * pixelRatio,
{},
Vec4B(RayColor,
(int)(RayMinUnscaledAlpha + std::abs(m_rayPerlin.get(angle * 626 + time * 30) * RayUnscaledAlphaVariance))
* sum
* alpha), 0.0f},
RenderVertex{start + Vec2F(std::cos(angle), std::sin(angle)) * length, {}, Vec4B(RayColor, 0), 0.0f});
}
void EnvironmentPainter::drawOrbiter(float pixelRatio, Vec2F const& screenSize, SkyRenderData const& sky, SkyOrbiter const& orbiter) {
float alpha = 1.0f;
Vec2F position;
// The way Starbound positions these is weird.
// It's a random point on a 400 by 400 area from the bottom left of the screen.
// That origin point is then multiplied by the zoom level.
// This does not intuitively scale with higher-resolution monitors, so lets fix that.
if (orbiter.type == SkyOrbiterType::Moon) {
const Vec2F correctionOrigin = { 320, 180 };
// correctionOrigin is 1920x1080 / default zoom level / 2, the most likely dev setup at the time.
Vec2F offset = orbiter.position - correctionOrigin;
position = (screenSize / 2) + offset * pixelRatio;
}
else
position = orbiter.position * pixelRatio;
if (orbiter.type == SkyOrbiterType::Sun) {
alpha = sky.dayLevel;
drawRays(pixelRatio, sky, position, std::max(screenSize[0], screenSize[1]), m_timer, sky.skyAlpha);
}
TexturePtr texture = m_textureGroup->loadTexture(orbiter.image);
Vec2F texSize = Vec2F(texture->size());
Mat3F renderMatrix = Mat3F::rotation(orbiter.angle, position);
RectF renderRect = RectF::withCenter(position, texSize * orbiter.scale * pixelRatio);
Vec4B renderColor = Vec4B(255, 255, 255, 255 * alpha);
m_renderer->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(), std::move(texture),
renderMatrix.transformVec2(renderRect.min()), Vec2F(0, 0),
renderMatrix.transformVec2(Vec2F{renderRect.xMax(), renderRect.yMin()}), Vec2F(texSize[0], 0),
renderMatrix.transformVec2(renderRect.max()), Vec2F(texSize[0], texSize[1]),
renderMatrix.transformVec2(Vec2F{renderRect.xMin(), renderRect.yMax()}), Vec2F(0, texSize[1]),
renderColor, 0.0f);
}
uint64_t EnvironmentPainter::starsHash(SkyRenderData const& sky, Vec2F const& viewSize) const {
XXHash64 hasher;
hasher.push(reinterpret_cast<char const*>(&viewSize[0]), sizeof(viewSize[0]));
hasher.push(reinterpret_cast<char const*>(&viewSize[1]), sizeof(viewSize[1]));
hasher.push(reinterpret_cast<char const*>(&sky.skyParameters.seed), sizeof(sky.skyParameters.seed));
hasher.push(reinterpret_cast<char const*>(&sky.type), sizeof(sky.type));
return hasher.digest();
}
void EnvironmentPainter::setupStars(SkyRenderData const& sky) {
if (!sky.settings)
return;
StringList starTypes = sky.starTypes();
size_t starTypesSize = starTypes.size();
m_starTextures.resize(starTypesSize * sky.starFrames);
for (size_t i = 0; i < starTypesSize; ++i) {
for (size_t j = 0; j < sky.starFrames; ++j)
m_starTextures[i * sky.starFrames + j] = m_textureGroup->loadTexture(starTypes[i] + ":" + toString(j));
}
int starCellSize = sky.settings.queryInt("stars.cellSize");
Vec2I starCount = jsonToVec2I(sky.settings.query("stars.cellCount"));
m_starGenerator = make_shared<Random2dPointGenerator<pair<size_t, float>>>(sky.skyParameters.seed, starCellSize, starCount);
JsonArray debrisFields = sky.settings.queryArray("spaceDebrisFields");
m_debrisGenerators.resize(debrisFields.size());
for (size_t i = 0; i < debrisFields.size(); ++i) {
int debrisCellSize = debrisFields[i].getInt("cellSize");
Vec2I debrisCountRange = jsonToVec2I(debrisFields[i].get("cellCountRange"));
uint64_t debrisSeed = staticRandomU64(sky.skyParameters.seed, i, "DebrisFieldSeed");
m_debrisGenerators[i] = make_shared<Random2dPointGenerator<pair<String, float>, double>>(debrisSeed, debrisCellSize, debrisCountRange);
}
}
}