356 lines
11 KiB
C++
356 lines
11 KiB
C++
#include <algorithm>
|
|
#include <filesystem>
|
|
#include <stb_image.h>
|
|
|
|
#include "TextureAtlas.h"
|
|
|
|
#include "util/Log.h"
|
|
#include "util/Util.h"
|
|
#include "game/atlas/asset/AtlasRef.h"
|
|
|
|
TextureAtlas::TextureAtlas(uvec2 size) :
|
|
canvasSize(size),
|
|
canvasTileSize(size / 16u),
|
|
atlasData(size.x * 4 * size.y),
|
|
maxTextureSlots(canvasTileSize.x * canvasTileSize.y),
|
|
empty(canvasTileSize.x * canvasTileSize.y, true) {
|
|
|
|
/**
|
|
* Rect Function.
|
|
* Creates a rectangular image with the color specified.
|
|
*
|
|
* @param w - The width of the rect.
|
|
* @param h - The height of the rect.
|
|
* @param fill - The color to fill the rectangle with.
|
|
*/
|
|
|
|
parser.addFn<u16, u16, string>("rect", [](u16 w, u16 h, string hex) {
|
|
vec4 color = Util::hexToColorVec(hex);
|
|
vec<u8> newData(w * h * 4);
|
|
for (u32 i = 0; i < w * h * 4; i++) newData[i] = color[i % 4] * 255;
|
|
return TexParserData { std::make_shared<RawTexData>(newData, uvec2 { w, h }) };
|
|
});
|
|
|
|
/**
|
|
* Alpha Function.
|
|
* Adjusts the opacity of the provided image by a factor.
|
|
*
|
|
* @param factor - The factor that the opacity should be scaled by. 0 = transparent, 1 = opaque.
|
|
* @param tex - The texture to scale the alpha of.
|
|
*/
|
|
|
|
parser.addFn<f32, TexParserData>("alpha", [](f32 factor, TexParserData tex) {
|
|
for (u32 i = 0; i < tex.data->size.x * tex.data->size.y; i++)
|
|
tex.data->data[i * 4 + 3] = (tex.data->data[i * 4 + 3] / 255.f * factor) * 255;
|
|
return tex;
|
|
});
|
|
|
|
/**
|
|
* Crop Function.
|
|
* Crops the texture to the specified dimensions and position.
|
|
*
|
|
* @param x - The left edge of the crop on the base texture.
|
|
* @param y - The top edge of the crop on the base texture.
|
|
* @param w - The width of the crop.
|
|
* @param h - The height of the crop.
|
|
* @param tex - The texture to crop.
|
|
*/
|
|
|
|
parser.addFn<u16, u16, u16, u16, TexParserData>("crop", [](u16 x, u16 y, u16 w, u16 h, TexParserData tex) {
|
|
let newData = vec<u8>(w * h * 4);
|
|
for (u32 i = 0; i < w * h * 4; i++) newData[i] =
|
|
tex.data->data[((x + i / 4 % w) + (y + i / 4 / w) * tex.data->size.x) * 4 + i % 4];
|
|
|
|
tex.data->data = newData;
|
|
tex.data->size = { w, h };
|
|
return tex;
|
|
});
|
|
|
|
/**
|
|
* Multiply Function.
|
|
* Multiplies the texture by the specified color.
|
|
*
|
|
* @param hex - The hex code of the color to multiply the image by.
|
|
* @param tex - The texture to multiply.
|
|
*/
|
|
|
|
parser.addFn<string, TexParserData>("multiply", [](string hex, TexParserData tex) {
|
|
vec4 multiple = Util::hexToColorVec(hex);
|
|
|
|
for (u32 i = 0; i < tex.data->size.x * tex.data->size.y * 4; i++)
|
|
tex.data->data[i] *= multiple[i % 4];
|
|
|
|
return tex;
|
|
});
|
|
|
|
/**
|
|
* Stack function. ()
|
|
* Stacks the provided textures on top of each other, blending partially opaque pixels.
|
|
* Requires at least two textures. The maximum is currently eight textures,
|
|
* however that could be removed later with some tweaks to StringParser.
|
|
*
|
|
* @param b - The base texture to stack onto.
|
|
* @param s1 - A texture to stack on top of the base texture.
|
|
* @param s2 - Another texture to stack on top of s1.
|
|
* @param s3 - ...
|
|
*/
|
|
|
|
parser.addFn<TexParserData, TexParserData, optional<TexParserData>,
|
|
optional<TexParserData>, optional<TexParserData>, optional<TexParserData>,
|
|
optional<TexParserData>, optional<TexParserData>, optional<TexParserData>>("", [](
|
|
TexParserData b, TexParserData s1, optional<TexParserData> s2,
|
|
optional<TexParserData> s3, optional<TexParserData> s4, optional<TexParserData> s5,
|
|
optional<TexParserData> s6, optional<TexParserData> s7, optional<TexParserData> s8) {
|
|
|
|
vec<TexParserData> stack;
|
|
stack.reserve(8);
|
|
stack.emplace_back(s1);
|
|
if (s2) stack.emplace_back(*s2);
|
|
if (s3) stack.emplace_back(*s3);
|
|
if (s4) stack.emplace_back(*s4);
|
|
if (s5) stack.emplace_back(*s5);
|
|
if (s6) stack.emplace_back(*s6);
|
|
if (s7) stack.emplace_back(*s7);
|
|
if (s8) stack.emplace_back(*s8);
|
|
|
|
for (const let& s : stack) {
|
|
for (u32 i = 0; i < b.data->size.x * b.data->size.y; i++) {
|
|
const f32 factor = s.data->data[i * 4 + 3] / 255.f;
|
|
b.data->data[i * 4] = b.data->data[i * 4] * (1 - factor) + s.data->data[i * 4] * factor;
|
|
b.data->data[i * 4 + 1] = b.data->data[i * 4 + 1] * (1 - factor) + s.data->data[i * 4 + 1] * factor;
|
|
b.data->data[i * 4 + 2] = b.data->data[i * 4 + 2] * (1 - factor) + s.data->data[i * 4 + 2] * factor;
|
|
b.data->data[i * 4 + 3] = std::min((u16)(b.data->data[i * 4 + 3]) + s.data->data[i * 4 + 3], 255);
|
|
}
|
|
}
|
|
|
|
return b;
|
|
});
|
|
|
|
/**
|
|
* Literal Function.
|
|
* Converts raw strings to texture data by indexing the atlas.
|
|
*
|
|
* @param tex - The string of the texture to convert.
|
|
*/
|
|
|
|
parser.addLiteralFnCtx([](TexParser::Ctx& ctx, string tex) {
|
|
return TexParser::Data { std::make_shared<RawTexData>(ctx.atlas.getBytesOfTex(tex)) };
|
|
});
|
|
|
|
// 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(&atlasData[0], size.x, size.y);
|
|
|
|
createMissingTexture();
|
|
}
|
|
|
|
vec<sptr<AtlasRef>> TextureAtlas::loadDirectory(const string& path, bool base) {
|
|
vec<sptr<AtlasRef>> refs {};
|
|
|
|
for (let file : std::filesystem::recursive_directory_iterator(path))
|
|
if (file.path().extension() == ".png") refs.push_back(
|
|
loadImage(file.path().string(), file.path().stem().string(), base));
|
|
|
|
return refs;
|
|
}
|
|
|
|
sptr<AtlasRef> TextureAtlas::loadImage(const string& path, const string& name, bool base) {
|
|
i32 width, height;
|
|
u8* rawData = stbi_load(path.data(), &width, &height, nullptr, 4);
|
|
vec<u8> data(rawData, rawData + width * height * 4);
|
|
free(rawData);
|
|
let ref = addImage(name, base, u16vec2(width, height), data);
|
|
return ref;
|
|
}
|
|
|
|
void TextureAtlas::update() {
|
|
for (let it = textures.begin(); it != textures.end();) {
|
|
if (!it->second->base && it->second.unique()) {
|
|
deleteImage(it->second);
|
|
it = textures.erase(it);
|
|
}
|
|
else it++;
|
|
}
|
|
}
|
|
|
|
vec4 TextureAtlas::getPixel(const sptr<AtlasRef>& ref, ivec2 pos) {
|
|
uvec2 absPos = { ref->rawPos.x + pos.x, ref->rawPos.y + pos.y };
|
|
u32 index = (static_cast<u32>(absPos.y) * canvasSize.x + static_cast<u32>(absPos.x)) * 4;
|
|
|
|
return { atlasData[index] / 255.f, atlasData[index + 1] / 255.f,
|
|
atlasData[index + 2] / 255.f, atlasData[index + 3] / 255.f };
|
|
}
|
|
|
|
sptr<AtlasRef> TextureAtlas::addImage(const string& name, bool base, u16vec2 size, vec<u8> data) {
|
|
u16vec2 tileSize = u16vec2(glm::ceil(vec2(size) / 16.f));
|
|
|
|
let posOpt = findImageSpace(tileSize);
|
|
if (!posOpt) throw std::runtime_error("Failed to find space in the dynamic definition atlas.");
|
|
u16vec2 pos = *posOpt;
|
|
|
|
textureSlotsUsed += tileSize.x * tileSize.y;
|
|
|
|
textures.erase(name);
|
|
let ref = make_shared<AtlasRef>(AtlasRef {
|
|
pos,
|
|
tileSize,
|
|
uvec2(pos) * 16u,
|
|
size,
|
|
vec4(pos.x * 16 / static_cast<f32>(canvasSize.x), pos.y * 16 / static_cast<f32>(canvasSize.y),
|
|
(pos.x * 16 + size.x) / static_cast<f32>(canvasSize.x), (pos.y * 16 + size.y) / static_cast<f32>(canvasSize.y)),
|
|
name,
|
|
base
|
|
});
|
|
|
|
textures.insert({ name, ref });
|
|
|
|
updateAtlas(ref->rawPos, ref->rawSize, data);
|
|
return ref;
|
|
}
|
|
|
|
sptr<AtlasRef> TextureAtlas::generateCrackImage(const string& name, u8 crackLevel) {
|
|
RawTexData base = getBytesOfTex(name);
|
|
|
|
std::string crackStr("zeus:default:crack_" + std::to_string(crackLevel));
|
|
RawTexData crack = getBytesOfTex(crackStr);
|
|
|
|
for (int i = 0; i < base.size.x * base.size.y; i++) {
|
|
float alpha = crack.data[i * 4 + 3] / 255.f;
|
|
|
|
base.data[i * 4 + 0] = static_cast<u8>(base.data[i * 4 + 0] * (1 - alpha) + crack.data[i * 4 + 0] * alpha);
|
|
base.data[i * 4 + 1] = static_cast<u8>(base.data[i * 4 + 1] * (1 - alpha) + crack.data[i * 4 + 1] * alpha);
|
|
base.data[i * 4 + 2] = static_cast<u8>(base.data[i * 4 + 2] * (1 - alpha) + crack.data[i * 4 + 2] * alpha);
|
|
}
|
|
|
|
auto ref = addImage(name + "_crack_" + std::to_string(crackLevel), false, base.size, base.data);
|
|
return ref;
|
|
}
|
|
|
|
sptr<AtlasRef> TextureAtlas::operator[](const string& name) {
|
|
if (textures.count(name)) return textures[name];
|
|
|
|
sptr<AtlasRef> gen = generateTexture(name);
|
|
if (gen) return gen;
|
|
|
|
throw std::runtime_error("Invalid texture: '" + name + "'");
|
|
}
|
|
|
|
sptr<AtlasRef> TextureAtlas::generateTexture(string req) {
|
|
TexParserCtx ctx { *this };
|
|
const let data = parser.parse(req, ctx);
|
|
return addImage(req, false, data.data->size, data.data->data);
|
|
}
|
|
|
|
TextureAtlas::RawTexData TextureAtlas::getBytesOfTex(const string& name) {
|
|
let it = textures.find(name);
|
|
if (it != textures.end()) return getBytesAtPos(it->second->rawPos, it->second->rawSize);
|
|
std::cout << Log::err << "Invalid base texture '" << name << "'." << Log::endl;
|
|
let& missing = textures["_missing"];
|
|
return getBytesAtPos(missing->rawPos, missing->rawSize);
|
|
}
|
|
|
|
TextureAtlas::RawTexData TextureAtlas::getBytesAtPos(uvec2 pos, uvec2 size) {
|
|
RawTexData tex { vec<u8>(size.x * size.y * 4), size };
|
|
|
|
for (u32 i = 0; i < size.x * size.y; i++) {
|
|
u32 x = pos.x + (i % size.x);
|
|
u32 y = pos.y + (i / size.x);
|
|
|
|
tex.data[i * 4 + 0] = atlasData[x * 4 + y * (canvasSize.x * 4)];
|
|
tex.data[i * 4 + 1] = atlasData[x * 4 + 1 + y * (canvasSize.x * 4)];
|
|
tex.data[i * 4 + 2] = atlasData[x * 4 + 2 + y * (canvasSize.x * 4)];
|
|
tex.data[i * 4 + 3] = atlasData[x * 4 + 3 + y * (canvasSize.x * 4)];
|
|
}
|
|
|
|
return tex;
|
|
}
|
|
|
|
optional<u16vec2> TextureAtlas::findImageSpace(u16vec2 tileSize) {
|
|
for (u16 j = 0; j < canvasTileSize.y - (tileSize.y - 1); j++) {
|
|
for (u16 i = 0; i < canvasTileSize.x - (tileSize.x - 1); i++) {
|
|
if (empty[j * canvasTileSize.x + i]) {
|
|
bool space = true;
|
|
|
|
for (int k = 0; k < tileSize.y; k++) {
|
|
for (int l = 0; l < tileSize.x; l++) {
|
|
if (!empty[(j + k) * canvasTileSize.x + (i + l)]) {
|
|
space = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!space) break;
|
|
}
|
|
|
|
if (space) {
|
|
for (int k = 0; k < tileSize.y; k++) {
|
|
for (int l = 0; l < tileSize.x; l++) {
|
|
empty[(j + k) * canvasTileSize.x + (i + l)] = false;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
addImage("_missing", true, u16vec2(16), data);
|
|
}
|
|
|
|
void TextureAtlas::updateAtlas(u16vec2 pos, u16vec2 size, vec<u8> data) {
|
|
texture.updateTexture(pos.x, pos.y, size.x, size.y, data.data());
|
|
|
|
for (u32 i = 0; i < size.x * size.y * 4; i++) {
|
|
u32 x = (i / 4) % size.x;
|
|
u32 y = (i / 4) / size.x;
|
|
u32 of = i % 4;
|
|
|
|
atlasData[(pos.x + x + (pos.y + y) * canvasSize.x) * 4 + of] = data[(x + y * size.x) * 4 + of];
|
|
}
|
|
}
|
|
|
|
void TextureAtlas::deleteImage(sptr<AtlasRef> ref) {
|
|
// Actually delete the image from the texture (for debugging)
|
|
|
|
//auto data = new unsigned char[ref->width * ref->height * 4];
|
|
//
|
|
//for (int i = 0; i < ref->width * ref->height * 4; i++) {
|
|
// data[i] = 0;
|
|
//}
|
|
//
|
|
//updateAtlas(ref->tileX, ref->tileY, ref->width, ref->height, data);
|
|
//delete[] data;
|
|
|
|
textureSlotsUsed -= ref->size.x * ref->size.y;
|
|
|
|
for (u32 x = ref->pos.x; x < ref->pos.x + ref->size.x; x++) {
|
|
for (u32 y = ref->pos.y; y < ref->pos.y + ref->size.y; y++) {
|
|
empty[y * canvasTileSize.x + x] = true;
|
|
}
|
|
}
|
|
} |