189 lines
6.1 KiB
C++
189 lines
6.1 KiB
C++
#include <algorithm>
|
|
#include <stb_image.h>
|
|
|
|
#include "TextureAtlas.h"
|
|
|
|
#include "util/Log.h"
|
|
#include "util/Util.h"
|
|
#include "game/atlas/TextureBuilder.h"
|
|
#include "game/atlas/asset/AtlasTexture.h"
|
|
|
|
TextureAtlas::TextureAtlas(uvec2 size) :
|
|
canvasSize(size),
|
|
canvasTileSize(size / 16u),
|
|
tilesTotal(canvasTileSize.x * canvasTileSize.y),
|
|
tiles(canvasTileSize.x * canvasTileSize.y, false),
|
|
builder(make_shared<TextureBuilder>(*this)) {
|
|
|
|
// Get GPU texture capabilites and log it.
|
|
i32 maxTexSize, texUnits;
|
|
|
|
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
|
|
std::cout << Log::info << "This GPU's max texture size is: " << maxTexSize << "px^2." << Log::endl;
|
|
|
|
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &texUnits);
|
|
std::cout << Log::info << "This GPU supports " << texUnits << " texture units." << Log::endl;
|
|
|
|
// Initialize the texture atlas.
|
|
texture.loadFromBytes(&vec<u8>(size.x * size.y * 4)[0], size.x, size.y);
|
|
createMissingTexture();
|
|
}
|
|
|
|
vec<AtlasRef> TextureAtlas::addDirectory(const std::filesystem::path& path, bool persistent) {
|
|
vec<AtlasRef> refs {};
|
|
for (let file : std::filesystem::recursive_directory_iterator(path))
|
|
if (file.path().extension() == ".png") refs.push_back(addFile(file.path(), persistent));
|
|
return refs;
|
|
}
|
|
|
|
AtlasRef TextureAtlas::addFile(const std::filesystem::path& path, bool persistent, string identifier) {
|
|
if (identifier.empty()) identifier = path.stem().string();
|
|
i32 width, height;
|
|
u8* rawData = stbi_load(path.string().data(), &width, &height, nullptr, 4);
|
|
vec<u8> data(rawData, rawData + width * height * 4);
|
|
free(rawData);
|
|
let ref = addBytes(identifier, persistent, u16vec2(width, height), data);
|
|
return ref;
|
|
}
|
|
|
|
AtlasRef TextureAtlas::addBytes(const string& identifier, bool persistent, u16vec2 size, const vec<u8>& data) {
|
|
let tileSize = u16vec2(glm::ceil(vec2(size) / 16.f));
|
|
let posOpt = findAtlasSpace(tileSize);
|
|
if (!posOpt) throw std::runtime_error("Failed to find space in the dynamic definition atlas.");
|
|
u16vec2 pos = *posOpt * static_cast<u16>(16);
|
|
|
|
tilesUsed += tileSize.x * tileSize.y;
|
|
textures.erase(identifier);
|
|
AtlasRef ref = sptr<AtlasTexture>(new AtlasTexture(*this, identifier, pos, size, data),
|
|
[this](AtlasTexture* tex) { removeTexture(tex); });
|
|
if (persistent) persistentTextures.insert(ref);
|
|
addTexture(ref);
|
|
|
|
return ref;
|
|
}
|
|
|
|
sptr<AtlasTexture> TextureAtlas::addCrop(const string& identifier, bool persistent,
|
|
u16vec2 offset, u16vec2 size, const sptr<AtlasTexture>& source) {
|
|
|
|
textures.erase(identifier);
|
|
AtlasRef ref = sptr<AtlasTexture>(new AtlasTexture(*this, identifier, offset, size, source),
|
|
[this](AtlasTexture* tex) { removeTexture(tex); });
|
|
if (persistent) persistentTextures.insert(ref);
|
|
addTexture(ref);
|
|
|
|
return ref;
|
|
}
|
|
|
|
AtlasRef TextureAtlas::operator[](const string& identifier) {
|
|
const let tex = get(identifier);
|
|
if (tex->getIdentifier() != "_missing") return tex;
|
|
|
|
let data = builder->build(identifier);
|
|
let texture = (std::holds_alternative<TextureBuilder::Texture::ByteData>(data.texture->data))
|
|
? addBytes(identifier, false, data.texture->size, data.texture->getBytes())
|
|
: addCrop(identifier, false, std::get<TextureBuilder::Texture::CropData>(data.texture->data).offset,
|
|
data.texture->size, std::get<TextureBuilder::Texture::CropData>(data.texture->data).source);
|
|
|
|
if (data.tintMask) {
|
|
let mask = (std::holds_alternative<TextureBuilder::Texture::ByteData>(data.tintMask->second->data))
|
|
? addBytes(identifier + ":tint", false, data.texture->size, data.tintMask->second->getBytes())
|
|
: addCrop(identifier + ":tint", false,
|
|
std::get<TextureBuilder::Texture::CropData>(data.tintMask->second->data).offset,
|
|
data.texture->size, std::get<TextureBuilder::Texture::CropData>(data.tintMask->second->data).source);
|
|
texture->setTintData(data.tintMask->first, mask);
|
|
}
|
|
|
|
return texture;
|
|
}
|
|
|
|
AtlasRef TextureAtlas::get(const string& identifier) const {
|
|
if (textures.count(identifier)) return textures.at(identifier).lock();
|
|
return textures.at("_missing").lock();
|
|
}
|
|
|
|
const uvec2 TextureAtlas::getCanvasSize() {
|
|
return canvasSize;
|
|
}
|
|
|
|
optional<u16vec2> TextureAtlas::findAtlasSpace(u16vec2 tileSize) {
|
|
for (u16 j = 0; j < canvasTileSize.y - (tileSize.y - 1); j++) {
|
|
for (u16 i = 0; i < canvasTileSize.x - (tileSize.x - 1); i++) {
|
|
bool space = true;
|
|
|
|
for (u16 k = 0; k < tileSize.y; k++) {
|
|
for (u16 l = 0; l < tileSize.x; l++) {
|
|
if (tiles[(j + k) * canvasTileSize.x + (i + l)]) {
|
|
space = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!space) break;
|
|
}
|
|
|
|
if (space) {
|
|
for (u16 k = 0; k < tileSize.y; k++)
|
|
for (u16 l = 0; l < tileSize.x; l++)
|
|
tiles[(j + k) * canvasTileSize.x + (i + l)] = true;
|
|
return u16vec2(i, j);
|
|
}
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void TextureAtlas::createMissingTexture() {
|
|
let data = vec<u8>(16 * 4 * 16);
|
|
for (u16 i = 0; i < 16 * 16; i++) {
|
|
u8 m = 0;
|
|
if ((i % 16 < 8) ^ ((i / 16) < 8)) m = 255;
|
|
|
|
data[i * 4 + 0] = m;
|
|
data[i * 4 + 1] = 0;
|
|
data[i * 4 + 2] = m;
|
|
data[i * 4 + 3] = 255;
|
|
}
|
|
|
|
addBytes("_missing", true, u16vec2(16), data);
|
|
}
|
|
|
|
void TextureAtlas::addTexture(const AtlasRef& tex) {
|
|
let pos = tex->getPos();
|
|
let size = tex->getSize();
|
|
textures.insert({ tex->getIdentifier(), std::weak_ptr(tex) });
|
|
if (!tex->isCrop()) texture.updateTexture(pos.x, pos.y, size.x, size.y, tex->getBytes().data());
|
|
}
|
|
|
|
void TextureAtlas::removeTexture(const AtlasTexture* tex) {
|
|
std::cerr << "Removed " << tex->getIdentifier() << std::endl;
|
|
textures.erase(tex->getIdentifier());
|
|
if (tex->isCrop()) return;
|
|
|
|
let tilePos = tex->getTilePos();
|
|
let tileSize = tex->getTileSize();
|
|
|
|
tilesUsed -= tileSize.x * tileSize.y;
|
|
|
|
for (u16 x = tilePos.x; x < tilePos.x + tileSize.x; x++)
|
|
for (u16 y = tilePos.y; y < tilePos.y + tileSize.y; y++)
|
|
tiles[y * canvasTileSize.x + x] = false;
|
|
|
|
// For debugging
|
|
vec<u8> clear(tileSize.x * 16 * tileSize.y * 16 * 4);
|
|
texture.updateTexture(tilePos.x * 16, tilePos.y * 16, tileSize.x * 16, tileSize.y * 16, clear.data());
|
|
|
|
delete tex;
|
|
}
|
|
|
|
const u32 TextureAtlas::getTilesUsed() {
|
|
return tilesUsed;
|
|
}
|
|
|
|
const u32 TextureAtlas::getTilesTotal() {
|
|
return tilesTotal;
|
|
}
|
|
|
|
const Texture& TextureAtlas::getTexture() {
|
|
return texture;
|
|
} |