2023-06-20 04:33:09 +00:00
|
|
|
#include "StarEnvironmentPainter.hpp"
|
|
|
|
#include "StarLexicalCast.hpp"
|
|
|
|
#include "StarTime.hpp"
|
|
|
|
#include "StarXXHash.hpp"
|
|
|
|
#include "StarJsonExtra.hpp"
|
2023-06-29 18:34:10 +00:00
|
|
|
#include "StarLogging.hpp"
|
|
|
|
#include "StarMathCommon.hpp"
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
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;
|
2023-06-29 22:37:27 +00:00
|
|
|
int const EnvironmentPainter::RayCount = 60;
|
2023-06-20 04:33:09 +00:00
|
|
|
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 = move(renderer);
|
|
|
|
m_textureGroup = make_shared<AssetTextureGroup>(m_renderer->createTextureGroup(TextureGroupSize::Large));
|
|
|
|
m_timer = 0;
|
|
|
|
m_lastTime = 0;
|
|
|
|
m_rayPerlin = PerlinF(1, RayPerlinFrequency, RayPerlinAmplitude, 0, 2.0f, 2.0f, Random::randu64());
|
|
|
|
}
|
|
|
|
|
|
|
|
void EnvironmentPainter::update() {
|
|
|
|
// Allows the rays to change alpha with time.
|
|
|
|
int64_t currentTime = Time::monotonicMilliseconds();
|
|
|
|
m_timer += (currentTime - m_lastTime) / 1000.0;
|
|
|
|
m_timer = std::fmod(m_timer, Constants::pi * 100000.0);
|
|
|
|
m_lastTime = currentTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-06-29 18:34:10 +00:00
|
|
|
auto& primitives = m_renderer->immediatePrimitives();
|
|
|
|
|
2023-06-25 06:10:57 +00:00
|
|
|
for (auto& star : stars) {
|
2023-06-20 04:33:09 +00:00
|
|
|
Vec2F screenPos = transform.transformVec2(star.first);
|
|
|
|
if (viewRect.contains(screenPos)) {
|
2023-06-25 06:10:57 +00:00
|
|
|
size_t starFrame = (size_t)(sky.epochTime + star.second.second) % sky.starFrames;
|
2023-06-20 04:33:09 +00:00
|
|
|
auto const& texture = m_starTextures[star.second.first * sky.starFrames + starFrame];
|
2023-06-29 18:34:10 +00:00
|
|
|
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), texture, screenPos * pixelRatio - Vec2F(texture->size()) / 2, 1.0, color, 0.0f);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2023-06-29 21:12:22 +00:00
|
|
|
Vec2F viewMin = sky.starOffset - viewCenter;
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
Vec2F velocityOffset = -Vec2F(debrisXVel, debrisYVel) * sky.epochTime;
|
|
|
|
|
2023-06-29 18:34:10 +00:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2023-06-29 22:37:27 +00:00
|
|
|
float screenBuffer = ceil((float)biggest.max() * (float)Constants::sqrt2);
|
2023-06-29 21:12:22 +00:00
|
|
|
PolyF field = PolyF(RectF::withSize(viewMin + velocityOffset, viewSize).padded(screenBuffer));
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
Vec2F debrisAngularVelocityRange = jsonToVec2F(debrisField.query("angularVelocityRange"));
|
|
|
|
|
|
|
|
auto debrisItems = m_debrisGenerators[i]->generate(field,
|
|
|
|
[&](RandomSource& rand) {
|
2023-06-29 18:34:10 +00:00
|
|
|
StringView debrisImage = *rand.randFrom(imageOptions).stringPtr();
|
2023-06-20 04:33:09 +00:00
|
|
|
float debrisAngularVelocity = rand.randf(debrisAngularVelocityRange[0], debrisAngularVelocityRange[1]);
|
|
|
|
|
2023-06-29 18:34:10 +00:00
|
|
|
return pair<StringView, float>(debrisImage, debrisAngularVelocity);
|
2023-06-20 04:33:09 +00:00
|
|
|
});
|
|
|
|
|
2023-06-29 21:12:22 +00:00
|
|
|
Vec2F debrisPositionOffset = viewMin + velocityOffset;
|
2023-06-20 04:33:09 +00:00
|
|
|
|
2023-06-29 18:34:10 +00:00
|
|
|
for (auto& debrisItem : debrisItems) {
|
2023-06-29 21:12:22 +00:00
|
|
|
Vec2F debrisPosition = rotMatrix.transformVec2(debrisItem.first - debrisPositionOffset);
|
2023-06-20 04:33:09 +00:00
|
|
|
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;
|
|
|
|
|
2023-06-29 18:34:10 +00:00
|
|
|
auto& primitives = m_renderer->immediatePrimitives();
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
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);
|
|
|
|
|
2023-06-29 18:34:10 +00:00
|
|
|
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), 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>(), 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);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2023-06-29 18:34:10 +00:00
|
|
|
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});
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
// Flash overlay for Interstellar travel
|
|
|
|
Vec4B flashColor = sky.flashColor.toRgba();
|
2023-06-29 18:34:10 +00:00
|
|
|
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), RectF(Vec2F(), screenSize), flashColor, 0.0f);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
m_renderer->flush();
|
|
|
|
}
|
|
|
|
|
2023-06-29 19:55:41 +00:00
|
|
|
// TODO: Fix this to work with decimal zoom levels. Currently, the clouds shake rapidly when interpolating between zoom levels.
|
2023-06-20 04:33:09 +00:00
|
|
|
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
|
|
|
|
|
2023-06-29 18:34:10 +00:00
|
|
|
auto& primitives = m_renderer->immediatePrimitives();
|
|
|
|
|
2023-06-25 06:10:57 +00:00
|
|
|
for (auto& layer : layers) {
|
2023-06-20 04:33:09 +00:00
|
|
|
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};
|
2023-06-25 06:10:57 +00:00
|
|
|
|
|
|
|
AssetPath first = layer.textures.first();
|
|
|
|
first.directives += layer.directives;
|
|
|
|
Vec2F parallaxSize = Vec2F(m_textureGroup->loadTexture(first)->size());
|
2023-06-20 04:33:09 +00:00
|
|
|
Vec2F parallaxPixels = parallaxSize * camera.pixelRatio();
|
|
|
|
|
|
|
|
// texture offset in *screen pixel space*
|
|
|
|
Vec2F parallaxOffset = layer.parallaxOffset * camera.pixelRatio();
|
|
|
|
if (layer.speed != 0) {
|
|
|
|
double drift = fmod((double)layer.speed * (sky.epochTime / (double)sky.dayLength) * camera.pixelRatio(), (double)parallaxPixels[0]);
|
|
|
|
parallaxOffset[0] = fmod(parallaxOffset[0] + drift, parallaxPixels[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
|
2023-06-25 06:10:57 +00:00
|
|
|
for (auto const& textureImage : layer.textures) {
|
|
|
|
AssetPath withDirectives = textureImage;
|
|
|
|
withDirectives.directives += layer.directives;
|
|
|
|
if (auto texture = m_textureGroup->tryTexture(withDirectives)) {
|
2023-06-20 04:33:09 +00:00
|
|
|
RectF drawRect = RectF::withSize(anchorPoint, subImage.size() * camera.pixelRatio());
|
2023-06-29 18:34:10 +00:00
|
|
|
primitives.emplace_back(std::in_place_type_t<RenderQuad>(), 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});
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2023-06-29 18:34:10 +00:00
|
|
|
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,
|
2023-06-20 04:33:09 +00:00
|
|
|
{},
|
|
|
|
Vec4B(RayColor,
|
|
|
|
(int)(RayMinUnscaledAlpha + std::abs(m_rayPerlin.get(angle * 896 + time * 30) * RayUnscaledAlphaVariance))
|
|
|
|
* sum
|
|
|
|
* alpha), 0.0f},
|
2023-06-29 18:34:10 +00:00
|
|
|
RenderVertex{start + Vec2F(std::cos(angle), std::sin(angle)) * SunRadius * pixelRatio,
|
2023-06-20 04:33:09 +00:00
|
|
|
{},
|
|
|
|
Vec4B(RayColor,
|
|
|
|
(int)(RayMinUnscaledAlpha + std::abs(m_rayPerlin.get(angle * 626 + time * 30) * RayUnscaledAlphaVariance))
|
|
|
|
* sum
|
|
|
|
* alpha), 0.0f},
|
2023-06-29 18:34:10 +00:00
|
|
|
RenderVertex{start + Vec2F(std::cos(angle), std::sin(angle)) * length, {}, Vec4B(RayColor, 0), 0.0f});
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void EnvironmentPainter::drawOrbiter(float pixelRatio, Vec2F const& screenSize, SkyRenderData const& sky, SkyOrbiter const& orbiter) {
|
|
|
|
float alpha = 1.0f;
|
2023-06-29 22:37:27 +00:00
|
|
|
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;
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-06-29 18:34:10 +00:00
|
|
|
m_renderer->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(), move(texture),
|
2023-06-29 21:12:22 +00:00
|
|
|
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);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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>>>(debrisSeed, debrisCellSize, debrisCountRange);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|