Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/rendering/StarWorldPainter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/rendering/StarWorldPainter.cpp')
-rw-r--r--source/rendering/StarWorldPainter.cpp296
1 files changed, 296 insertions, 0 deletions
diff --git a/source/rendering/StarWorldPainter.cpp b/source/rendering/StarWorldPainter.cpp
new file mode 100644
index 0000000..a063f36
--- /dev/null
+++ b/source/rendering/StarWorldPainter.cpp
@@ -0,0 +1,296 @@
+#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 = move(renderer);
+ auto textureGroup = m_renderer->createTextureGroup(TextureGroupSize::Large);
+ m_textPainter = make_shared<TextPainter>(m_assets->font("/hobo.ttf")->clone(), 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 const& WorldPainter::camera() const {
+ return m_camera;
+}
+
+void WorldPainter::render(WorldRenderData& renderData) {
+ m_camera.setScreenSize(m_renderer->screenSize());
+ m_camera.setPixelRatio(Root::singleton().configuration()->get("zoomLevel").toFloat());
+
+ m_assets = Root::singleton().assets();
+
+ m_environmentPainter->update();
+
+ m_tilePainter->setup(m_camera, renderData);
+
+ if (renderData.isFullbright) {
+ m_renderer->setEffectTexture("lightMap", Image::filled(Vec2U(1, 1), {255, 255, 255, 255}, PixelFormat::RGB24));
+ m_renderer->setEffectParameter("lightMapMultiplier", 1.0f);
+ } else {
+ m_tilePainter->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);
+ }
+
+ // Stars, Debris Fields, Sky, and Orbiters
+
+ m_environmentPainter->renderStars(m_camera.pixelRatio(), Vec2F(m_camera.screenSize()), renderData.skyRenderData);
+ m_environmentPainter->renderDebrisFields(m_camera.pixelRatio(), Vec2F(m_camera.screenSize()), renderData.skyRenderData);
+ m_environmentPainter->renderBackOrbiters(m_camera.pixelRatio(), Vec2F(m_camera.screenSize()), renderData.skyRenderData);
+ m_environmentPainter->renderPlanetHorizon(m_camera.pixelRatio(), Vec2F(m_camera.screenSize()), renderData.skyRenderData);
+ m_environmentPainter->renderSky(Vec2F(m_camera.screenSize()), renderData.skyRenderData);
+ m_environmentPainter->renderFrontOrbiters(m_camera.pixelRatio(), Vec2F(m_camera.screenSize()), renderData.skyRenderData);
+
+ // 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, 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(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::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());
+
+ for (Particle const& particle : renderData.particles) {
+ if (layer != particle.layer)
+ continue;
+
+ Vec2F position = m_camera.worldToScreen(particle.position);
+
+ if (!particleRenderWindow.contains(position))
+ continue;
+
+ Vec2I size = Vec2I::filled(particle.size * m_camera.pixelRatio());
+
+ if (particle.type == Particle::Type::Ember) {
+ m_renderer->render(renderFlatRect(RectF(position - Vec2F(size) / 2, position + Vec2F(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->render(RenderQuad{{},
+ {position - sideHalf, {}, color, lightMapMultiplier},
+ {position + sideHalf, {}, color, lightMapMultiplier},
+ {position - dir * length + sideHalf, {}, color, lightMapMultiplier},
+ {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.string, 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().addDirectives(particle.directives, true);
+ drawable.fullbright = particle.fullbright;
+ drawable.color = particle.color;
+ drawable.rotate(particle.rotation);
+ drawable.scale(particle.size);
+ drawable.translate(particle.position);
+ drawDrawable(move(drawable));
+
+ } else if (particle.type == Particle::Type::Text) {
+ Vec2F position = m_camera.worldToScreen(particle.position);
+ unsigned size = textParticleFontSize * m_camera.pixelRatio() * particle.size;
+ if (size > 0) {
+ m_textPainter->setFontSize(size);
+ m_textPainter->setFontColor(particle.color.toRgba());
+ 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(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(move(overlayDrawable));
+ }
+ }
+ } else {
+ for (auto& d : drawables)
+ drawDrawable(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(move(drawable));
+
+ m_renderer->flush();
+}
+
+}