178 lines
6.2 KiB
C++
178 lines
6.2 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "common/Exception.h"
|
|
#include "filesystem/FileData.h"
|
|
#include "graphics/Color.h"
|
|
#include "graphics/Graphics.h"
|
|
#include "image/Image.h"
|
|
#include "image/ImageData.h"
|
|
#include "spritestack/SpriteStack.h"
|
|
#include "window/Window.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <optional>
|
|
|
|
using eve::graphics::Color;
|
|
using eve::graphics::Graphics;
|
|
using eve::image::ImageData;
|
|
using eve::spritestack::SpriteStack;
|
|
using eve::spritestack::SpriteStack2D;
|
|
using eve::spritestack::SpriteStackBatch;
|
|
using Colorf = ImageData::Colorf;
|
|
|
|
namespace {
|
|
int countAlpha(const ImageData *img) {
|
|
int count = 0;
|
|
for (int y = 0; y < img->getHeight(); ++y)
|
|
for (int x = 0; x < img->getWidth(); ++x)
|
|
if (img->getPixel(x, y).a > 0.5f) ++count;
|
|
return count;
|
|
}
|
|
|
|
std::string testOutDir() { return std::string(EVENGINE_TEST_BINARY_DIR) + "/out"; }
|
|
|
|
void savePng(ImageData *frame, const std::string &path) {
|
|
REQUIRE(frame != nullptr);
|
|
[[maybe_unused]] auto *const imageModule = eve::image::Image::create();
|
|
std::unique_ptr<eve::filesystem::FileData> png(
|
|
frame->encode(ImageData::FormatHandler::ENCODED_PNG, "spritestack2d.png", false));
|
|
REQUIRE(png.get() != nullptr);
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
|
|
std::ofstream out(path, std::ios::binary);
|
|
out.write(static_cast<const char *>(png->getData()), std::streamsize(png->getSize()));
|
|
REQUIRE(out.good());
|
|
}
|
|
|
|
struct WindowFixture {
|
|
eve::window::Window *win = eve::window::Window::create();
|
|
Graphics *gfx = Graphics::create();
|
|
WindowFixture() {
|
|
REQUIRE(win != nullptr);
|
|
REQUIRE(gfx != nullptr);
|
|
eve::window::WindowSettings settings;
|
|
settings.width = 320;
|
|
settings.height = 240;
|
|
settings.centered = true;
|
|
REQUIRE(win->setWindowSettings(settings));
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.07f, 0.09f, 0.12f, 1.f));
|
|
}
|
|
~WindowFixture() { win->close(); }
|
|
};
|
|
|
|
std::unique_ptr<ImageData> renderStack(Graphics *gfx, const SpriteStack2D &stack) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
gfx->clear(std::nullopt, std::nullopt, std::nullopt);
|
|
stack.render(gfx);
|
|
gfx->present();
|
|
}
|
|
return std::unique_ptr<ImageData>(gfx->newImageData());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("spritestack.slice.boxLayers") {
|
|
auto layers = SpriteStack::create()->slicePrimitive("box", 6, 48, 48, "y", 0.f);
|
|
REQUIRE_EQ(layers.size(), size_t(6));
|
|
for (ImageData *layer : layers) {
|
|
REQUIRE(layer != nullptr);
|
|
CHECK_GT(countAlpha(layer), 100);
|
|
delete layer;
|
|
}
|
|
}
|
|
|
|
TEST_CASE("spritestack.slice.sphereVariesAcrossLayers") {
|
|
auto layers = SpriteStack::create()->slicePrimitive("sphere", 9, 64, 64, "y", 0.f);
|
|
REQUIRE_EQ(layers.size(), size_t(9));
|
|
const int edge = countAlpha(layers.front());
|
|
const int middle = countAlpha(layers[layers.size() / 2]);
|
|
CHECK_GT(middle, edge);
|
|
for (ImageData *layer : layers) delete layer;
|
|
}
|
|
|
|
TEST_CASE("spritestack.render.pure2DStack") {
|
|
WindowFixture fx;
|
|
auto layers = SpriteStack::create()->slicePrimitive("cone", 14, 64, 64, "y", 0.f);
|
|
SpriteStack2D stack;
|
|
stack.setLayerCount(int(layers.size()));
|
|
for (int i = 0; i < int(layers.size()); ++i) stack.setLayerImage(fx.gfx, layers[size_t(i)], i);
|
|
stack.setPosition(160.f, 155.f);
|
|
stack.setSize(96.f, 96.f);
|
|
stack.setThickness(3.f);
|
|
stack.setRotation(25.f);
|
|
stack.setTint(0.44f, 0.78f, 0.58f, 1.f);
|
|
stack.setShadowEnabled(true);
|
|
stack.setShadowOffset(8.f, 6.f);
|
|
stack.setOutline(1.5f, 0.02f, 0.03f, 0.04f);
|
|
|
|
auto frame = renderStack(fx.gfx, stack);
|
|
REQUIRE(frame.get() != nullptr);
|
|
savePng(frame.get(), testOutDir() + "/sprite_stack_2d.png");
|
|
int changed = 0;
|
|
for (int y = 30; y < 210; ++y) {
|
|
for (int x = 60; x < 260; ++x) {
|
|
const Colorf pixel = frame->getPixel(x, y);
|
|
if (pixel.g > 0.20f && pixel.g > pixel.r * 1.15f) ++changed;
|
|
}
|
|
}
|
|
CHECK_GT(changed, 500);
|
|
for (ImageData *layer : layers) delete layer;
|
|
}
|
|
|
|
TEST_CASE("spritestack.render.rotationChangesPixels") {
|
|
WindowFixture fx;
|
|
auto layers = SpriteStack::create()->slicePrimitive("box", 8, 48, 48, "y", 0.f);
|
|
SpriteStack2D stack;
|
|
stack.setLayerCount(int(layers.size()));
|
|
for (int i = 0; i < int(layers.size()); ++i) stack.setLayerImage(fx.gfx, layers[size_t(i)], i);
|
|
stack.setPosition(160.f, 140.f);
|
|
stack.setSize(100.f, 68.f);
|
|
stack.setThickness(3.f);
|
|
auto before = renderStack(fx.gfx, stack);
|
|
stack.setRotation(37.f);
|
|
auto after = renderStack(fx.gfx, stack);
|
|
int diff = 0;
|
|
for (int y = 0; y < before->getHeight(); ++y)
|
|
for (int x = 0; x < before->getWidth(); ++x) {
|
|
const Colorf a = before->getPixel(x, y);
|
|
const Colorf b = after->getPixel(x, y);
|
|
if (std::fabs(a.r - b.r) + std::fabs(a.g - b.g) + std::fabs(a.b - b.b) > 0.08f)
|
|
++diff;
|
|
}
|
|
CHECK_GT(diff, 300);
|
|
for (ImageData *layer : layers) delete layer;
|
|
}
|
|
|
|
TEST_CASE("spritestack.render.batchUses2DStacks") {
|
|
WindowFixture fx;
|
|
auto layers = SpriteStack::create()->slicePrimitive("sphere", 8, 48, 48, "y", 0.f);
|
|
SpriteStack2D left;
|
|
SpriteStack2D right;
|
|
for (SpriteStack2D *stack : {&left, &right}) {
|
|
stack->setLayerCount(int(layers.size()));
|
|
for (int i = 0; i < int(layers.size()); ++i) stack->setLayerImage(fx.gfx, layers[size_t(i)], i);
|
|
stack->setSize(70.f, 70.f);
|
|
stack->setThickness(2.f);
|
|
}
|
|
left.setPosition(105.f, 145.f);
|
|
right.setPosition(215.f, 145.f);
|
|
right.setRotation(30.f);
|
|
SpriteStackBatch batch;
|
|
batch.add(&left);
|
|
batch.add(&right);
|
|
CHECK_EQ(batch.getStackCount(), 2);
|
|
for (int i = 0; i < 3; ++i) {
|
|
fx.gfx->clear(std::nullopt, std::nullopt, std::nullopt);
|
|
batch.render(fx.gfx);
|
|
fx.gfx->present();
|
|
}
|
|
std::unique_ptr<ImageData> frame(fx.gfx->newImageData());
|
|
savePng(frame.get(), testOutDir() + "/sprite_stack_2d_batch.png");
|
|
for (ImageData *layer : layers) delete layer;
|
|
}
|