Files
EVEngine/test/particles_reference_effects.cpp
T

180 lines
6.7 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "graphics/Graphics.h"
#include "particles/ParticleEmitter.h"
#include "particles/ParticleSystem.h"
#include "particles/Particles.h"
#include "window/Window.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <filesystem>
#include <string>
#include <vector>
using namespace eve::particles;
namespace {
eve::graphics::Texture* makeVfxTexture(eve::graphics::Graphics* gfx, int size, bool ring) {
std::vector<std::uint8_t> pixels(std::size_t(size * size) * 4u);
for (int y = 0; y < size; ++y) {
for (int x = 0; x < size; ++x) {
const float nx = (float(x) + 0.5f) / float(size) * 2.f - 1.f;
const float ny = (float(y) + 0.5f) / float(size) * 2.f - 1.f;
const float r = std::sqrt(nx * nx + ny * ny);
const float a = ring ? std::clamp(1.f - std::abs(r - 0.68f) * 13.f, 0.f, 1.f)
: std::clamp(1.f - r, 0.f, 1.f) * std::clamp(1.4f - r, 0.f, 1.f);
auto* pixel = &pixels[std::size_t(y * size + x) * 4u];
const auto value = std::uint8_t(a * 255.f);
pixel[0] = value;
pixel[1] = value;
pixel[2] = value;
pixel[3] = value;
}
}
return gfx->newTexture(size, size, pixels.data());
}
ParticleEmitter* makeLayer(eve::graphics::Texture* texture, float x, float y, int capacity) {
auto* emitter = Particles::create()->newEmitter(capacity);
emitter->setTexture(texture);
emitter->setPosition(x, y);
emitter->setEmissionRate(0.f);
emitter->setRandomSeed(20260826 + int(x));
emitter->setAutoRandomSeed(false);
return emitter;
}
struct Palette {
float r;
float g;
float b;
};
void makeCompositeBurst(eve::graphics::Texture* glow, eve::graphics::Texture* ring, float x, float y, float direction,
const Palette& color, std::vector<ParticleEmitter*>& emitters) {
auto* core = makeLayer(glow, x, y, 4);
core->setParticleLifetime(2.f, 2.f);
core->setParticleSize(42.f, 42.f);
core->setSpeed(0.f, 0.f);
core->setBlendMode("additive");
core->setColorStart(color.r, color.g, color.b, 0.95f);
core->setColorEnd(color.r, color.g, color.b, 0.95f);
core->emit(1);
emitters.push_back(core);
auto* shockwave = makeLayer(ring, x, y, 4);
shockwave->setParticleLifetime(2.f, 2.f);
shockwave->setParticleSize(176.f, 176.f);
shockwave->setSpeed(0.f, 0.f);
shockwave->setBlendMode("additive");
shockwave->setColorStart(color.r, color.g, color.b, 0.82f);
shockwave->setColorEnd(color.r, color.g, color.b, 0.82f);
shockwave->emit(1);
emitters.push_back(shockwave);
auto* sparks = makeLayer(glow, x, y, 96);
sparks->setParticleLifetime(0.75f, 1.15f);
sparks->setParticleSize(7.f, 24.f);
sparks->setSizes(1.f, 0.15f);
sparks->setDirection(direction);
sparks->setSpread(0.72f);
sparks->setSpeed(150.f, 285.f);
sparks->setDamping(0.12f);
sparks->setRenderMode("velocity", 0.075f);
sparks->setBlendMode("additive");
sparks->setColorStart(1.f, 0.95f, 0.72f, 1.f);
sparks->setColorEnd(color.r, color.g, color.b, 0.f);
sparks->emit(48);
emitters.push_back(sparks);
auto* smoke = makeLayer(glow, x - std::cos(direction) * 18.f, y - std::sin(direction) * 18.f, 48);
smoke->setParticleLifetime(1.2f, 1.9f);
smoke->setParticleSize(18.f, 18.f);
smoke->setSizes(0.65f, 1.2f);
smoke->setSizeVariation(0.35f);
smoke->setDirection(direction + 3.14159265f);
smoke->setSpread(1.15f);
smoke->setSpeed(18.f, 58.f);
smoke->setDamping(0.3f);
smoke->setBlendMode("additive");
smoke->setColorStart(color.r * 0.18f, color.g * 0.16f, color.b * 0.22f, 0.22f);
smoke->setColorEnd(0.02f, 0.025f, 0.055f, 0.f);
smoke->emit(12);
emitters.push_back(smoke);
}
float luma(const Color& color) { return color.r * 0.2126f + color.g * 0.7152f + color.b * 0.0722f; }
} // namespace
TEST_CASE("particles.reference.compositedBurstsRenderAtMultipleAngles") {
auto* window = eve::window::Window::create();
auto* gfx = eve::graphics::Graphics::create();
REQUIRE(window != nullptr);
REQUIRE(gfx != nullptr);
eve::window::WindowSettings settings;
settings.width = 960;
settings.height = 420;
settings.centered = true;
REQUIRE(window->setWindowSettings(settings));
gfx->setScreenReadbackEnabled(true);
auto* glow = makeVfxTexture(gfx, 64, false);
auto* ring = makeVfxTexture(gfx, 64, true);
REQUIRE(glow != nullptr);
REQUIRE(ring != nullptr);
constexpr std::array<float, 3> centers = {170.f, 480.f, 790.f};
constexpr std::array<float, 3> angles = {0.f, 0.78539816f, 1.57079633f};
constexpr std::array<Palette, 3> palettes = {
Palette{0.08f, 0.78f, 1.f},
Palette{1.f, 0.28f, 0.06f},
Palette{0.62f, 0.16f, 1.f},
};
std::vector<ParticleEmitter*> emitters;
for (std::size_t i = 0; i < centers.size(); ++i)
makeCompositeBurst(glow, ring, centers[i], 210.f, angles[i], palettes[i], emitters);
for (int frame = 0; frame < 18; ++frame) ParticleSimSystem::update(1.f / 60.f);
gfx->setBackgroundColorRGBA(0.006f, 0.009f, 0.024f, 1.f);
for (int frame = 0; frame < 3; ++frame) {
gfx->clearScreen();
for (std::size_t i = 0; i < centers.size(); ++i) {
gfx->drawSolidRect(centers[i] - 142.f, 40.f, 284.f, 340.f, Color(0.f, 0.f, 0.f, 1.f));
gfx->drawSolidRect(centers[i] - 142.f, 40.f, 284.f, 2.f,
Color(palettes[i].r, palettes[i].g, palettes[i].b, 0.8f));
}
ParticleRenderSystem::render(gfx);
gfx->present();
}
const std::string output = std::string(EVENGINE_TEST_BINARY_DIR) + "/particle_composite_reference_angles.png";
CHECK(gfx->saveFramePng(output));
CHECK(std::filesystem::exists(output));
REQUIRE(std::filesystem::file_size(output) > std::uintmax_t(12000));
for (std::size_t i = 0; i < centers.size(); ++i) {
float peakLuma = 0.f;
float peakChroma = 0.f;
for (int y = 120; y <= 300; y += 4) {
for (int x = int(centers[i]) - 110; x <= int(centers[i]) + 110; x += 4) {
const Color sample = gfx->getPixel(x, y);
peakLuma = std::max(peakLuma, luma(sample));
peakChroma = std::max(
peakChroma, std::max({sample.r, sample.g, sample.b}) - std::min({sample.r, sample.g, sample.b}));
}
}
REQUIRE_GT(peakLuma, 0.3f);
REQUIRE_GT(peakChroma, 0.12f);
}
for (auto* emitter : emitters) emitter->release();
window->close();
}