v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
296
source/rendering/StarWorldPainter.cpp
Normal file
296
source/rendering/StarWorldPainter.cpp
Normal file
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue