osb/source/rendering/StarWorldPainter.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

325 lines
14 KiB
C++

#include "StarWorldPainter.hpp"
#include "StarAnimation.hpp"
#include "StarRoot.hpp"
#include "StarConfiguration.hpp"
#include "StarAssets.hpp"
#include "StarJsonExtra.hpp"
namespace Star {
WorldPainter::WorldPainter() {
m_assets = Root::singleton().assets();
m_camera.setScreenSize({800, 600});
m_camera.setCenterWorldPosition(Vec2F());
m_camera.setPixelRatio(Root::singleton().configuration()->get("zoomLevel").toFloat());
m_highlightConfig = m_assets->json("/highlights.config");
for (auto p : m_highlightConfig.get("highlightDirectives").iterateObject())
m_highlightDirectives.set(EntityHighlightEffectTypeNames.getLeft(p.first), {p.second.getString("underlay", ""), p.second.getString("overlay", "")});
m_entityBarOffset = jsonToVec2F(m_assets->json("/rendering.config:entityBarOffset"));
m_entityBarSpacing = jsonToVec2F(m_assets->json("/rendering.config:entityBarSpacing"));
m_entityBarSize = jsonToVec2F(m_assets->json("/rendering.config:entityBarSize"));
m_entityBarIconOffset = jsonToVec2F(m_assets->json("/rendering.config:entityBarIconOffset"));
m_preloadTextureChance = m_assets->json("/rendering.config:preloadTextureChance").toFloat();
}
void WorldPainter::renderInit(RendererPtr renderer) {
m_assets = Root::singleton().assets();
m_renderer = std::move(renderer);
auto textureGroup = m_renderer->createTextureGroup(TextureGroupSize::Large);
m_textPainter = make_shared<TextPainter>(m_renderer, textureGroup);
m_tilePainter = make_shared<TilePainter>(m_renderer);
m_drawablePainter = make_shared<DrawablePainter>(m_renderer, make_shared<AssetTextureGroup>(textureGroup));
m_environmentPainter = make_shared<EnvironmentPainter>(m_renderer);
}
void WorldPainter::setCameraPosition(WorldGeometry const& geometry, Vec2F const& position) {
m_camera.setWorldGeometry(geometry);
m_camera.setCenterWorldPosition(position);
}
WorldCamera& WorldPainter::camera() {
return m_camera;
}
void WorldPainter::update(float dt) {
m_environmentPainter->update(dt);
}
void WorldPainter::render(WorldRenderData& renderData, function<void()> lightWaiter) {
m_camera.setScreenSize(m_renderer->screenSize());
m_camera.setTargetPixelRatio(Root::singleton().configuration()->get("zoomLevel").toFloat());
m_assets = Root::singleton().assets();
m_tilePainter->setup(m_camera, renderData);
// Stars, Debris Fields, Sky, and Orbiters
// Use a fixed pixel ratio for certain things.
float pixelRatioBasis = m_camera.screenSize()[1] / 1080.0f;
float starAndDebrisRatio = lerp(0.0625f, pixelRatioBasis * 2.0f, m_camera.pixelRatio());
float orbiterAndPlanetRatio = lerp(0.125f, pixelRatioBasis * 3.0f, m_camera.pixelRatio());
m_environmentPainter->renderStars(starAndDebrisRatio, Vec2F(m_camera.screenSize()), renderData.skyRenderData);
m_environmentPainter->renderDebrisFields(starAndDebrisRatio, Vec2F(m_camera.screenSize()), renderData.skyRenderData);
m_environmentPainter->renderBackOrbiters(orbiterAndPlanetRatio, Vec2F(m_camera.screenSize()), renderData.skyRenderData);
m_environmentPainter->renderPlanetHorizon(orbiterAndPlanetRatio, Vec2F(m_camera.screenSize()), renderData.skyRenderData);
m_environmentPainter->renderSky(Vec2F(m_camera.screenSize()), renderData.skyRenderData);
m_environmentPainter->renderFrontOrbiters(orbiterAndPlanetRatio, Vec2F(m_camera.screenSize()), renderData.skyRenderData);
m_renderer->flush();
if (lightWaiter) {
auto start = Time::monotonicMicroseconds();
lightWaiter();
LogMap::set("client_render_world_async_light_wait", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - start));
}
if (renderData.isFullbright) {
m_renderer->setEffectTexture("lightMap", Image::filled(Vec2U(1, 1), { 255, 255, 255, 255 }, PixelFormat::RGB24));
m_renderer->setEffectTexture("tileLightMap", Image::filled(Vec2U(1, 1), { 0, 0, 0, 0 }, PixelFormat::RGBA32));
m_renderer->setEffectParameter("lightMapMultiplier", 1.0f);
} else {
adjustLighting(renderData);
m_renderer->setEffectParameter("lightMapMultiplier", m_assets->json("/rendering.config:lightMapMultiplier").toFloat());
m_renderer->setEffectParameter("lightMapScale", Vec2F::filled(TilePixels * m_camera.pixelRatio()));
m_renderer->setEffectParameter("lightMapOffset", m_camera.worldToScreen(Vec2F(renderData.lightMinPosition)));
m_renderer->setEffectTexture("lightMap", renderData.lightMap);
m_renderer->setEffectTexture("tileLightMap", renderData.tileLightMap);
}
// Parallax layers
auto parallaxDelta = m_camera.worldGeometry().diff(m_camera.centerWorldPosition(), m_previousCameraCenter);
if (parallaxDelta.magnitude() > 10)
m_parallaxWorldPosition = m_camera.centerWorldPosition();
else
m_parallaxWorldPosition += parallaxDelta;
m_previousCameraCenter = m_camera.centerWorldPosition();
m_parallaxWorldPosition[1] = m_camera.centerWorldPosition()[1];
if (!renderData.parallaxLayers.empty())
m_environmentPainter->renderParallaxLayers(m_parallaxWorldPosition, m_camera, renderData.parallaxLayers, renderData.skyRenderData);
// Main world layers
Map<EntityRenderLayer, List<pair<EntityHighlightEffect, List<Drawable>>>> entityDrawables;
for (auto& ed : renderData.entityDrawables) {
for (auto& p : ed.layers)
entityDrawables[p.first].append({ed.highlightEffect, std::move(p.second)});
}
auto entityDrawableIterator = entityDrawables.begin();
auto renderEntitiesUntil = [this, &entityDrawables, &entityDrawableIterator](Maybe<EntityRenderLayer> until) {
while (true) {
if (entityDrawableIterator == entityDrawables.end())
break;
if (until && entityDrawableIterator->first >= *until)
break;
for (auto& edl : entityDrawableIterator->second)
drawEntityLayer(std::move(edl.second), edl.first);
++entityDrawableIterator;
}
m_renderer->flush();
};
renderEntitiesUntil(RenderLayerBackgroundOverlay);
drawDrawableSet(renderData.backgroundOverlays);
renderEntitiesUntil(RenderLayerBackgroundTile);
m_tilePainter->renderBackground(m_camera);
renderEntitiesUntil(RenderLayerPlatform);
m_tilePainter->renderMidground(m_camera);
renderEntitiesUntil(RenderLayerBackParticle);
renderParticles(renderData, Particle::Layer::Back);
renderEntitiesUntil(RenderLayerLiquid);
m_tilePainter->renderLiquid(m_camera);
renderEntitiesUntil(RenderLayerMiddleParticle);
renderParticles(renderData, Particle::Layer::Middle);
renderEntitiesUntil(RenderLayerForegroundTile);
m_tilePainter->renderForeground(m_camera);
renderEntitiesUntil(RenderLayerForegroundOverlay);
drawDrawableSet(renderData.foregroundOverlays);
renderEntitiesUntil(RenderLayerFrontParticle);
renderParticles(renderData, Particle::Layer::Front);
renderEntitiesUntil(RenderLayerOverlay);
drawDrawableSet(renderData.nametags);
renderBars(renderData);
renderEntitiesUntil({});
auto dimLevel = round(renderData.dimLevel * 255);
if (dimLevel != 0)
m_renderer->render(renderFlatRect(RectF::withSize({}, Vec2F(m_camera.screenSize())), Vec4B(renderData.dimColor, dimLevel), 0.0f));
int64_t textureTimeout = m_assets->json("/rendering.config:textureTimeout").toInt();
m_textPainter->cleanup(textureTimeout);
m_drawablePainter->cleanup(textureTimeout);
m_environmentPainter->cleanup(textureTimeout);
m_tilePainter->cleanup();
}
void WorldPainter::adjustLighting(WorldRenderData& renderData) {
m_tilePainter->adjustLighting(renderData);
}
void WorldPainter::renderParticles(WorldRenderData& renderData, Particle::Layer layer) {
const int textParticleFontSize = m_assets->json("/rendering.config:textParticleFontSize").toInt();
const RectF particleRenderWindow = RectF::withSize(Vec2F(), Vec2F(m_camera.screenSize())).padded(m_assets->json("/rendering.config:particleRenderWindowPadding").toInt());
if (!renderData.particles)
return;
for (Particle const& particle : *renderData.particles) {
if (layer != particle.layer)
continue;
Vec2F position = m_camera.worldToScreen(particle.position);
if (!particleRenderWindow.contains(position))
continue;
Vec2F size = Vec2F::filled(particle.size * m_camera.pixelRatio());
if (particle.type == Particle::Type::Ember) {
m_renderer->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(),
RectF(position - size / 2, position + size / 2),
particle.color.toRgba(),
particle.fullbright ? 0.0f : 1.0f);
} else if (particle.type == Particle::Type::Streak) {
// Draw a rotated quad streaking in the direction the particle is coming from.
// Sadly this looks awful.
Vec2F dir = particle.velocity.normalized();
Vec2F sideHalf = dir.rot90() * m_camera.pixelRatio() * particle.size / 2;
float length = particle.length * m_camera.pixelRatio();
Vec4B color = particle.color.toRgba();
float lightMapMultiplier = particle.fullbright ? 0.0f : 1.0f;
m_renderer->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(),
position - sideHalf,
position + sideHalf,
position - dir * length + sideHalf,
position - dir * length - sideHalf,
color, lightMapMultiplier);
} else if (particle.type == Particle::Type::Textured || particle.type == Particle::Type::Animated) {
Drawable drawable;
if (particle.type == Particle::Type::Textured)
drawable = Drawable::makeImage(particle.image, 1.0f / TilePixels, true, Vec2F(0, 0));
else
drawable = particle.animation->drawable(1.0f / TilePixels);
if (particle.flip && particle.flippable)
drawable.scale(Vec2F(-1, 1));
if (drawable.isImage())
drawable.imagePart().addDirectivesGroup(particle.directives, true);
drawable.fullbright = particle.fullbright;
drawable.color = particle.color;
drawable.rotate(particle.rotation);
drawable.scale(particle.size);
drawable.translate(particle.position);
drawDrawable(std::move(drawable));
} else if (particle.type == Particle::Type::Text) {
Vec2F position = m_camera.worldToScreen(particle.position);
int size = min(128.0f, round((float)textParticleFontSize * m_camera.pixelRatio() * particle.size));
if (size > 0) {
m_textPainter->setFontSize(size);
m_textPainter->setFontColor(particle.color.toRgba());
m_textPainter->setProcessingDirectives("");
m_textPainter->setFont("");
m_textPainter->renderText(particle.string, {position, HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor});
}
}
}
m_renderer->flush();
}
void WorldPainter::renderBars(WorldRenderData& renderData) {
auto offset = m_entityBarOffset;
for (auto const& bar : renderData.overheadBars) {
auto position = bar.entityPosition + offset;
offset += m_entityBarSpacing;
if (bar.icon) {
auto iconDrawPosition = position - (m_entityBarSize / 2).round() + m_entityBarIconOffset;
drawDrawable(Drawable::makeImage(*bar.icon, 1.0f / TilePixels, true, iconDrawPosition));
}
if (!bar.detailOnly) {
auto fullBar = RectF({}, {m_entityBarSize.x() * bar.percentage, m_entityBarSize.y()});
auto emptyBar = RectF({m_entityBarSize.x() * bar.percentage, 0.0f}, m_entityBarSize);
auto fullColor = bar.color;
auto emptyColor = Color::Black;
drawDrawable(Drawable::makePoly(PolyF(emptyBar), emptyColor, position));
drawDrawable(Drawable::makePoly(PolyF(fullBar), fullColor, position));
}
}
m_renderer->flush();
}
void WorldPainter::drawEntityLayer(List<Drawable> drawables, EntityHighlightEffect highlightEffect) {
highlightEffect.level *= m_highlightConfig.getFloat("maxHighlightLevel", 1.0);
if (m_highlightDirectives.contains(highlightEffect.type) && highlightEffect.level > 0) {
// first pass, draw underlay
auto underlayDirectives = m_highlightDirectives[highlightEffect.type].first;
if (!underlayDirectives.empty()) {
for (auto& d : drawables) {
if (d.isImage()) {
auto underlayDrawable = Drawable(d);
underlayDrawable.fullbright = true;
underlayDrawable.color = Color::rgbaf(1, 1, 1, highlightEffect.level);
underlayDrawable.imagePart().addDirectives(underlayDirectives, true);
drawDrawable(std::move(underlayDrawable));
}
}
}
// second pass, draw main drawables and overlays
auto overlayDirectives = m_highlightDirectives[highlightEffect.type].second;
for (auto& d : drawables) {
drawDrawable(d);
if (!overlayDirectives.empty() && d.isImage()) {
auto overlayDrawable = Drawable(d);
overlayDrawable.fullbright = true;
overlayDrawable.color = Color::rgbaf(1, 1, 1, highlightEffect.level);
overlayDrawable.imagePart().addDirectives(overlayDirectives, true);
drawDrawable(std::move(overlayDrawable));
}
}
} else {
for (auto& d : drawables)
drawDrawable(std::move(d));
}
}
void WorldPainter::drawDrawable(Drawable drawable) {
drawable.position = m_camera.worldToScreen(drawable.position);
drawable.scale(m_camera.pixelRatio() * TilePixels, drawable.position);
if (drawable.isLine())
drawable.linePart().width *= m_camera.pixelRatio();
// draw the drawable if it's on screen
// if it's not on screen, there's a random chance to pre-load
// pre-load is not done on every tick because it's expensive to look up images with long paths
if (RectF::withSize(Vec2F(), Vec2F(m_camera.screenSize())).intersects(drawable.boundBox(false)))
m_drawablePainter->drawDrawable(drawable);
else if (drawable.isImage() && Random::randf() < m_preloadTextureChance)
m_assets->tryImage(drawable.imagePart().image);
}
void WorldPainter::drawDrawableSet(List<Drawable>& drawables) {
for (Drawable& drawable : drawables)
drawDrawable(std::move(drawable));
m_renderer->flush();
}
}