diff --git a/res/textures/block.png b/res/textures/block.png deleted file mode 100644 index 2879bd8f..00000000 Binary files a/res/textures/block.png and /dev/null differ diff --git a/res/textures/blocks/air.png b/res/textures/blocks/air.png new file mode 100644 index 00000000..8b374250 Binary files /dev/null and b/res/textures/blocks/air.png differ diff --git a/res/textures/blocks/bedrock.png b/res/textures/blocks/bedrock.png new file mode 100644 index 00000000..9984e91a Binary files /dev/null and b/res/textures/blocks/bedrock.png differ diff --git a/res/textures/blocks/brick.png b/res/textures/blocks/brick.png new file mode 100644 index 00000000..2a40a162 Binary files /dev/null and b/res/textures/blocks/brick.png differ diff --git a/res/textures/blocks/dirt.png b/res/textures/blocks/dirt.png new file mode 100644 index 00000000..6239e67c Binary files /dev/null and b/res/textures/blocks/dirt.png differ diff --git a/res/textures/blocks/flower.png b/res/textures/blocks/flower.png new file mode 100644 index 00000000..38f070eb Binary files /dev/null and b/res/textures/blocks/flower.png differ diff --git a/res/textures/blocks/glass.png b/res/textures/blocks/glass.png new file mode 100644 index 00000000..1389f231 Binary files /dev/null and b/res/textures/blocks/glass.png differ diff --git a/res/textures/blocks/grass.png b/res/textures/blocks/grass.png new file mode 100644 index 00000000..d43769ca Binary files /dev/null and b/res/textures/blocks/grass.png differ diff --git a/res/textures/blocks/grass_side.png b/res/textures/blocks/grass_side.png new file mode 100644 index 00000000..802c1a87 Binary files /dev/null and b/res/textures/blocks/grass_side.png differ diff --git a/res/textures/blocks/grass_top.png b/res/textures/blocks/grass_top.png new file mode 100644 index 00000000..f2e7324b Binary files /dev/null and b/res/textures/blocks/grass_top.png differ diff --git a/res/textures/blocks/lamp.png b/res/textures/blocks/lamp.png new file mode 100644 index 00000000..eb53b8ca Binary files /dev/null and b/res/textures/blocks/lamp.png differ diff --git a/res/textures/blocks/leaves.png b/res/textures/blocks/leaves.png new file mode 100644 index 00000000..861fbcb7 Binary files /dev/null and b/res/textures/blocks/leaves.png differ diff --git a/res/textures/blocks/metal.png b/res/textures/blocks/metal.png new file mode 100644 index 00000000..fc1ac447 Binary files /dev/null and b/res/textures/blocks/metal.png differ diff --git a/res/textures/blocks/planks.png b/res/textures/blocks/planks.png new file mode 100644 index 00000000..76bd18d4 Binary files /dev/null and b/res/textures/blocks/planks.png differ diff --git a/res/textures/blocks/rust.png b/res/textures/blocks/rust.png new file mode 100644 index 00000000..21a7d4ba Binary files /dev/null and b/res/textures/blocks/rust.png differ diff --git a/res/textures/blocks/sand.png b/res/textures/blocks/sand.png new file mode 100644 index 00000000..3c2518c0 Binary files /dev/null and b/res/textures/blocks/sand.png differ diff --git a/res/textures/blocks/stone.png b/res/textures/blocks/stone.png new file mode 100644 index 00000000..5214e3e1 Binary files /dev/null and b/res/textures/blocks/stone.png differ diff --git a/res/textures/blocks/water.png b/res/textures/blocks/water.png new file mode 100644 index 00000000..00dac81b Binary files /dev/null and b/res/textures/blocks/water.png differ diff --git a/res/textures/blocks/wood.png b/res/textures/blocks/wood.png new file mode 100644 index 00000000..00757a50 Binary files /dev/null and b/res/textures/blocks/wood.png differ diff --git a/res/textures/blocks/wood_top.png b/res/textures/blocks/wood_top.png new file mode 100644 index 00000000..e9ad51ca Binary files /dev/null and b/res/textures/blocks/wood_top.png differ diff --git a/res/textures/slot.png b/res/textures/slot.png deleted file mode 100644 index 65c5d3ad..00000000 Binary files a/res/textures/slot.png and /dev/null differ diff --git a/src/assets/Assets.cpp b/src/assets/Assets.cpp index 459ac868..cb6c16aa 100644 --- a/src/assets/Assets.cpp +++ b/src/assets/Assets.cpp @@ -2,6 +2,7 @@ #include "../graphics/Texture.h" #include "../graphics/Shader.h" +#include "../graphics/Atlas.h" #include "../graphics/Font.h" Assets::~Assets() { @@ -16,6 +17,10 @@ Assets::~Assets() { for (auto& iter : fonts){ delete iter.second; } + + for (auto& iter : atlases) { + delete iter.second; + } } Texture* Assets::getTexture(std::string name) const { @@ -52,3 +57,14 @@ Font* Assets::getFont(std::string name) const { void Assets::store(Font* font, std::string name){ fonts[name] = font; } + +Atlas* Assets::getAtlas(std::string name) const { + auto found = atlases.find(name); + if (found == atlases.end()) + return nullptr; + return found->second; +} + +void Assets::store(Atlas* atlas, std::string name){ + atlases[name] = atlas; +} diff --git a/src/assets/Assets.h b/src/assets/Assets.h index e8122ff0..2bccb784 100644 --- a/src/assets/Assets.h +++ b/src/assets/Assets.h @@ -7,11 +7,13 @@ class Texture; class Shader; class Font; +class Atlas; class Assets { std::unordered_map textures; std::unordered_map shaders; std::unordered_map fonts; + std::unordered_map atlases; public: ~Assets(); Texture* getTexture(std::string name) const; @@ -22,6 +24,9 @@ public: Font* getFont(std::string name) const; void store(Font* font, std::string name); + + Atlas* getAtlas(std::string name) const; + void store(Atlas* atlas, std::string name); }; #endif /* ASSETS_ASSETS_H_ */ diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 2e62556e..f8125d2b 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -2,6 +2,7 @@ #include "Assets.h" #include +#include #include #include "../constants.h" @@ -42,6 +43,7 @@ bool AssetsLoader::loadNext() { #include "../graphics/Shader.h" #include "../graphics/ImageData.h" #include "../graphics/Texture.h" +#include "../graphics/Atlas.h" #include "../graphics/Font.h" bool _load_shader(Assets* assets, const std::string& filename, const std::string& name) { @@ -65,18 +67,19 @@ bool _load_texture(Assets* assets, const std::string& filename, const std::strin } bool _load_atlas(Assets* assets, const std::string& filename, const std::string& name) { - unique_ptr image (png::load_image(filename)); - if (image == nullptr) { - std::cerr << "failed to load image '" << name << "'" << std::endl; - return false; + AtlasBuilder builder; + for (const auto& entry : std::filesystem::directory_iterator(filename)) { + std::filesystem::path file = entry.path(); + if (file.extension() == ".png") { + std::string name = file.stem().string(); + std::unique_ptr image (png::load_image(file.string())); + //image->flipY(); + image->fixAlphaColor(); + builder.add(name, image.release()); + } } - for (int i = 0; i < ATLAS_MARGIN_SIZE; i++) { - ImageData* newimage = add_atlas_margins(image.get(), 16); - image.reset(newimage); - } - - Texture* texture = Texture::from(image.get()); - assets->store(texture, name); + Atlas* atlas = builder.build(2); + assets->store(atlas, name); return true; } @@ -107,9 +110,7 @@ void AssetsLoader::addDefaults(AssetsLoader& loader) { loader.add(ASSET_SHADER, SHADERS_FOLDER"/lines", "lines"); loader.add(ASSET_SHADER, SHADERS_FOLDER"/ui", "ui"); - loader.add(ASSET_ATLAS, TEXTURES_FOLDER"/block.png", "block"); - loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/block.png", "block_tex"); - loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/slot.png", "slot"); + loader.add(ASSET_ATLAS, TEXTURES_FOLDER"/blocks", "blocks"); loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/menubg.png", "menubg"); loader.add(ASSET_FONT, FONTS_FOLDER"/font", "normal"); diff --git a/src/constants.h b/src/constants.h index e2665635..9a471de4 100644 --- a/src/constants.h +++ b/src/constants.h @@ -18,8 +18,6 @@ inline uint vox_index(int x, int y, int z, int w=CHUNK_W, int d=CHUNK_D) { return (y * d + z) * w + x; } -#define ATLAS_MARGIN_SIZE 2 - #define RES_FLODER "res/" #define SHADERS_FOLDER "res/shaders" diff --git a/src/definitions.cpp b/src/definitions.cpp index eb4e68dd..3b810ea8 100644 --- a/src/definitions.cpp +++ b/src/definitions.cpp @@ -9,7 +9,7 @@ // All in-game definitions (blocks, items, etc..) void setup_definitions(ContentBuilder* builder) { // TODO: automatic atlas generation instead of using texture indices - Block* block = new Block("core:air", 0); + Block* block = new Block("core:air", "air"); block->drawGroup = 1; block->lightPassing = true; block->skyLightPassing = true; @@ -18,41 +18,41 @@ void setup_definitions(ContentBuilder* builder) { block->model = BlockModel::none; builder->add(block); - block = new Block("base:dirt", 2); + block = new Block("base:dirt", "dirt"); builder->add(block); - block = new Block("base:grass_block", 4); - block->textureFaces[2] = 2; - block->textureFaces[3] = 1; + block = new Block("base:grass_block", "grass_side"); + block->textureFaces[2] = "dirt"; + block->textureFaces[3] = "grass_top"; builder->add(block); - block = new Block("base:lamp", 3); + block = new Block("base:lamp", "lamp"); block->emission[0] = 15; block->emission[1] = 14; block->emission[2] = 13; builder->add(block); - block = new Block("base:glass",5); + block = new Block("base:glass", "glass"); block->drawGroup = 2; block->lightPassing = true; builder->add(block); - block = new Block("base:planks", 6); + block = new Block("base:planks", "planks"); builder->add(block); - block = new Block("base:wood", 7); - block->textureFaces[2] = 8; - block->textureFaces[3] = 8; + block = new Block("base:wood", "wood"); + block->textureFaces[2] = "wood_top"; + block->textureFaces[3] = "wood_top"; block->rotatable = true; builder->add(block); - block = new Block("base:leaves", 9); + block = new Block("base:leaves", "leaves"); builder->add(block); - block = new Block("base:stone", 10); + block = new Block("base:stone", "stone"); builder->add(block); - block = new Block("base:water", 11); + block = new Block("base:water", "water"); block->drawGroup = 4; block->lightPassing = true; block->skyLightPassing = false; @@ -60,14 +60,14 @@ void setup_definitions(ContentBuilder* builder) { block->selectable = false; builder->add(block); - block = new Block("base:sand", 12); + block = new Block("base:sand", "sand"); builder->add(block); - block = new Block("base:bedrock", 13); + block = new Block("base:bedrock", "bedrock"); block->breakable = false; builder->add(block); - block = new Block("base:grass", 14); + block = new Block("base:grass", "grass"); block->drawGroup = 5; block->lightPassing = true; block->obstacle = false; @@ -75,20 +75,20 @@ void setup_definitions(ContentBuilder* builder) { block->hitboxScale = 0.5f; builder->add(block); - block = new Block("base:flower", 16); + block = new Block("base:flower", "flower"); block->drawGroup = 5; block->lightPassing = true; block->obstacle = false; block->model = BlockModel::xsprite; builder->add(block); - block = new Block("base:brick", 17); + block = new Block("base:brick", "brick"); builder->add(block); - block = new Block("base:metal", 18); + block = new Block("base:metal", "metal"); builder->add(block); - block = new Block("base:rust", 19); + block = new Block("base:rust", "rust"); builder->add(block); } diff --git a/src/frontend/gui/controls.cpp b/src/frontend/gui/controls.cpp index 8b4935cb..c7a2f278 100644 --- a/src/frontend/gui/controls.cpp +++ b/src/frontend/gui/controls.cpp @@ -162,8 +162,7 @@ InputBindBox::InputBindBox(Binding& binding, vec4 padding) : Panel(vec2(100,32), padding, 0, false), binding(binding) { label = new Label(L""); - //label->align(Align::center); - add(shared_ptr(label)); + add(label); } shared_ptr InputBindBox::getAt(vec2 pos, shared_ptr self) { diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 42d3b145..c65751df 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -15,6 +15,7 @@ #include "../graphics/Shader.h" #include "../graphics/Batch2D.h" #include "../graphics/Font.h" +#include "../graphics/Atlas.h" #include "../graphics/Mesh.h" #include "../window/Camera.h" #include "../window/Window.h" @@ -146,6 +147,7 @@ void HudRenderer::drawContentAccess(const GfxContext& ctx, Player* player) { const Viewport& viewport = ctx.getViewport(); const uint width = viewport.getWidth(); + Atlas* atlas = assets->getAtlas("blocks"); uint count = contentIds->countBlockDefs(); uint icon_size = 48; @@ -171,7 +173,7 @@ void HudRenderer::drawContentAccess(const GfxContext& ctx, Player* player) { batch->rect(inv_x, inv_y, inv_w, inv_h); // blocks & items - batch->texture(assets->getTexture("block_tex")); + batch->texture(atlas->getTexture()); for (uint i = 0; i < count-1; i++) { Block* cblock = contentIds->getBlockDef(i+1); if (cblock == nullptr) @@ -190,9 +192,10 @@ void HudRenderer::drawContentAccess(const GfxContext& ctx, Player* player) { } if (cblock->model == BlockModel::block){ - batch->blockSprite(x, y, icon_size, icon_size, 16, cblock->textureFaces, tint); + batch->blockSprite(x, y, icon_size, icon_size, (const UVRegion*)cblock->uvdata, tint); } else if (cblock->model == BlockModel::xsprite){ - batch->sprite(x, y, icon_size, icon_size, 16, cblock->textureFaces[3], tint); + const UVRegion& region = (reinterpret_cast(cblock->uvdata))[3]; + batch->sprite(x, y, icon_size, icon_size, region, tint); } } } @@ -231,6 +234,8 @@ void HudRenderer::draw(const GfxContext& ctx){ const uint width = viewport.getWidth(); const uint height = viewport.getHeight(); + Atlas* atlas = assets->getAtlas("blocks"); + debugPanel->visible(level->player->debug); uicamera->fov = height; @@ -239,10 +244,9 @@ void HudRenderer::draw(const GfxContext& ctx){ uishader->use(); uishader->uniformMatrix("u_projview", uicamera->getProjection()*uicamera->getView()); - // Chosen block preview - Texture* blocks = assets->getTexture("block_tex"); + batch->begin(); - batch->texture(nullptr); + // Chosen block preview batch->color = vec4(1.0f); if (Events::_cursor_locked && !level->player->debug) { batch->lineWidth(2); @@ -258,14 +262,15 @@ void HudRenderer::draw(const GfxContext& ctx){ batch->rect(width - 68, height - 68, 68, 68); batch->color = vec4(1.0f); - batch->texture(blocks); + batch->texture(atlas->getTexture()); { Block* cblock = contentIds->getBlockDef(player->choosenBlock); assert(cblock != nullptr); if (cblock->model == BlockModel::block){ - batch->blockSprite(width-56, uicamera->fov - 56, 48, 48, 16, cblock->textureFaces, vec4(1.0f)); + batch->blockSprite(width-56, uicamera->fov - 56, 48, 48, (const UVRegion*)cblock->uvdata, vec4(1.0f)); } else if (cblock->model == BlockModel::xsprite){ - batch->sprite(width-56, uicamera->fov - 56, 48, 48, 16, cblock->textureFaces[3], vec4(1.0f)); + const UVRegion& region = (reinterpret_cast(cblock->uvdata))[3]; + batch->sprite(width-56, uicamera->fov - 56, 48, 48, region, vec4(1.0f)); } } diff --git a/src/frontend/world_render.cpp b/src/frontend/world_render.cpp index c06649ec..73862e8f 100644 --- a/src/frontend/world_render.cpp +++ b/src/frontend/world_render.cpp @@ -10,6 +10,7 @@ #include "../window/Window.h" #include "../window/Camera.h" #include "../graphics/Mesh.h" +#include "../graphics/Atlas.h" #include "../graphics/Shader.h" #include "../graphics/Texture.h" #include "../graphics/LineBatch.h" @@ -27,6 +28,7 @@ #include "../engine.h" using glm::vec3; +using std::string; using std::shared_ptr; WorldRenderer::WorldRenderer(Engine* engine, Level* level) @@ -37,6 +39,26 @@ WorldRenderer::WorldRenderer(Engine* engine, Level* level) level->events->listen(EVT_CHUNK_HIDDEN, [this](lvl_event_type type, Chunk* chunk) { renderer->unload(chunk); }); + + // TODO: move to some proper place + // otrefactoryu dnem + Assets* assets = engine->getAssets(); + Atlas* atlas = assets->getAtlas("blocks"); + const Content* content = level->content; + const ContentIndices* contentIds = content->indices; + for (uint i = 0; i < contentIds->countBlockDefs(); i++) { + Block* def = contentIds->getBlockDef(i); + for (uint side = 0; side < 6; side++) { + string tex = def->textureFaces[side]; + if (atlas->has(tex)) { + UVRegion region = atlas->get(tex); + float* data = reinterpret_cast(®ion); + for (uint j = 0; j < 4; j++) { + def->uvdata[side * 4 + j] = data[j]; + } + } + } + } } WorldRenderer::~WorldRenderer() { @@ -99,7 +121,7 @@ void WorldRenderer::draw(const GfxContext& pctx, Camera* camera, bool occlusion) const Content* content = level->content; const ContentIndices* contentIds = content->indices; Assets* assets = engine->getAssets(); - Texture* texture = assets->getTexture("block"); + Atlas* atlas = assets->getAtlas("blocks"); Shader* shader = assets->getShader("main"); Shader* linesShader = assets->getShader("lines"); @@ -139,7 +161,7 @@ void WorldRenderer::draw(const GfxContext& pctx, Camera* camera, bool occlusion) cblock->emission[1] / 15.0f * multiplier, cblock->emission[2] / 15.0f * multiplier); shader->uniform1f("u_torchlightDistance", 6.0f); - texture->bind(); + atlas->getTexture()->bind(); Chunks* chunks = level->chunks; drawChunks(chunks, camera, shader, occlusion); diff --git a/src/graphics/Atlas.cpp b/src/graphics/Atlas.cpp new file mode 100644 index 00000000..7db65bc6 --- /dev/null +++ b/src/graphics/Atlas.cpp @@ -0,0 +1,86 @@ +#include "Atlas.h" + +#include "../maths/LMPacker.h" +#include "Texture.h" +#include "ImageData.h" + +using std::vector; +using std::string; +using std::unique_ptr; +using std::shared_ptr; +using std::unordered_map; + +Atlas::Atlas(ImageData* image, + unordered_map regions) + : texture(Texture::from(image)), + image(image), + regions(regions) { +} + +Atlas::~Atlas() { + delete image; + delete texture; +} + +bool Atlas::has(string name) const { + return regions.find(name) != regions.end(); +} + +const UVRegion& Atlas::get(string name) const { + return regions.at(name); +} + +Texture* Atlas::getTexture() const { + return texture; +} + +ImageData* Atlas::getImage() const { + return image; +} + +void AtlasBuilder::add(string name, ImageData* image) { + entries.push_back(atlasentry{name, shared_ptr(image)}); +} + +Atlas* AtlasBuilder::build(uint extrusion) { + unique_ptr sizes (new uint[entries.size() * 2]); + for (uint i = 0; i < entries.size(); i++) { + auto& entry = entries[i]; + auto image = entry.image; + sizes[i*2] = image->getWidth(); + sizes[i*2+1] = image->getHeight(); + } + LMPacker packer(sizes.get(), entries.size()*2); + sizes.reset(nullptr); + + int width = 32; + int height = 32; + while (!packer.buildCompact(width, height, extrusion)) { + if (width > height) { + height *= 2; + } else { + width *= 2; + } + } + + unordered_map regions; + unique_ptr canvas (new ImageData(ImageFormat::rgba8888, width, height)); + vector rects = packer.getResult(); + for (uint i = 0; i < entries.size(); i++) { + const rectangle& rect = rects[i]; + const atlasentry& entry = entries[i]; + uint x = rect.x; + uint y = rect.y; + uint w = rect.width; + uint h = rect.height; + canvas->blit(entry.image.get(), rect.x, rect.y); + for (uint j = 0; j < extrusion; j++) { + canvas->extrude(x - j, y - j, w + j*2, h + j*2); + } + float unitX = 1.0f / width; + float unitY = 1.0f / height; + regions[entry.name] = UVRegion(unitX * x, unitY * y, + unitX * (x + w), unitY * (y + h)); + } + return new Atlas(canvas.release(), regions); +} \ No newline at end of file diff --git a/src/graphics/Atlas.h b/src/graphics/Atlas.h new file mode 100644 index 00000000..5da3ffa0 --- /dev/null +++ b/src/graphics/Atlas.h @@ -0,0 +1,44 @@ +#ifndef GRAPHICS_ATLAS_H_ +#define GRAPHICS_ATLAS_H_ + +#include +#include +#include +#include +#include "UVRegion.h" +#include "../typedefs.h" + +class ImageData; +class Texture; + +class Atlas { + Texture* texture; + ImageData* image; + std::unordered_map regions; +public: + Atlas(ImageData* image, std::unordered_map regions); + ~Atlas(); + + bool has(std::string name) const; + const UVRegion& get(std::string name) const; + + Texture* getTexture() const; + ImageData* getImage() const; +}; + + +struct atlasentry { + std::string name; + std::shared_ptr image; +}; + +class AtlasBuilder { + std::vector entries; +public: + AtlasBuilder() {} + void add(std::string name, ImageData* image); + + Atlas* build(uint extrusion); +}; + +#endif // GRAPHICS_ATLAS_H_ \ No newline at end of file diff --git a/src/graphics/Batch2D.cpp b/src/graphics/Batch2D.cpp index c27af864..6885e752 100644 --- a/src/graphics/Batch2D.cpp +++ b/src/graphics/Batch2D.cpp @@ -288,6 +288,10 @@ void Batch2D::sprite(Sprite* sprite) { sprite->color); } +void Batch2D::sprite(float x, float y, float w, float h, const UVRegion& region, vec4 tint){ + rect(x, y, w, h, region.u1, region.v1, region.u2-region.u1, region.v2-region.v1, tint.r, tint.g, tint.b, tint.a); +} + void Batch2D::sprite(float x, float y, float w, float h, int atlasRes, int index, vec4 tint){ float scale = 1.0f / (float)atlasRes; float u = (index % atlasRes) * scale; @@ -295,12 +299,16 @@ void Batch2D::sprite(float x, float y, float w, float h, int atlasRes, int index rect(x, y, w, h, u, v, scale, scale, tint.r, tint.g, tint.b, tint.a); } -void Batch2D::blockSprite(float x, float y, float w, float h, int atlasRes, int index[6], vec4 tint){ - float scale = 1.0f / (float)atlasRes; - float uu = (index[3] % atlasRes) * scale; - float vu = 1.0f - ((index[3] / atlasRes) * scale) - scale; - float uf = (index[0] % atlasRes) * scale; - float vf = 1.0f - ((index[0] / atlasRes) * scale) - scale; +void Batch2D::blockSprite(float x, float y, float w, float h, const UVRegion regions[], vec4 tint){ + // TODO: rewrite it + float uu = (regions[3].u1); + float vu = (regions[3].v1); + + float uf = (regions[0].u1); + float vf = (regions[0].v1); + + float scalex = regions[3].u2-regions[3].u1; + float scaley = regions[3].v2-regions[3].v1; if (this->index + 18*VERTEX_SIZE >= capacity) render(); @@ -317,13 +325,13 @@ void Batch2D::blockSprite(float x, float y, float w, float h, int atlasRes, int vec2(ox-sx, y+(h*0.75f))}; vec2 uvpoints[8] = {vec2(uu, vu), - vec2(uu+scale, vu), - vec2(uu+scale, vu+scale), - vec2(uu, vu+scale), + vec2(uu+scalex, vu), + vec2(uu+scalex, vu+scalex), + vec2(uu, vu+scalex), vec2(uf, vf), - vec2(uf+scale, vf), - vec2(uf+scale, vf+scale), - vec2(uf, vf+scale)}; + vec2(uf+scaley, vf), + vec2(uf+scaley, vf+scaley), + vec2(uf, vf+scaley)}; vertex(points[0], uvpoints[3], tint.r, tint.g, tint.b, tint.a); vertex(points[1], uvpoints[0], tint.r, tint.g, tint.b, tint.a); diff --git a/src/graphics/Batch2D.h b/src/graphics/Batch2D.h index 6d66106f..c19becf3 100644 --- a/src/graphics/Batch2D.h +++ b/src/graphics/Batch2D.h @@ -37,9 +37,10 @@ public: void begin(); void texture(Texture* texture); - void sprite(float x, float y, float w, float h, int atlasRes, int index, vec4 tint); + void sprite(float x, float y, float w, float h, const UVRegion& region, vec4 tint); void sprite(Sprite* sprite); - void blockSprite(float x, float y, float w, float h, int atlasRes, int index[6], vec4 tint); + void sprite(float x, float y, float w, float h, int atlasRes, int index, vec4 tint); + void blockSprite(float x, float y, float w, float h, const UVRegion regions[], vec4 tint); void point(float x, float y, float r, float g, float b, float a); void line(float x1, float y1, float x2, float y2, float r, float g, float b, float a); void rect(float x, float y, diff --git a/src/graphics/BlocksRenderer.cpp b/src/graphics/BlocksRenderer.cpp index 6dae363a..5067f23d 100644 --- a/src/graphics/BlocksRenderer.cpp +++ b/src/graphics/BlocksRenderer.cpp @@ -303,12 +303,7 @@ vec4 BlocksRenderer::pickSoftLight(int x, int y, int z, const ivec3& right, cons // Get texture atlas UV region for block face inline UVRegion uvfor(const Block& def, uint face, int atlas_size) { - float uvsize = 1.0f / (float)atlas_size; - float us = 1.0f / (float)atlas_size / (float)atlas_size * ATLAS_MARGIN_SIZE * 0.8f; - const uint id = def.textureFaces[face]; - float u = (id % atlas_size) * uvsize; - float v = 1.0f - (id / atlas_size + 1) * uvsize; - return UVRegion(u + us, v + us, u + uvsize - us, v + uvsize - us); + return *reinterpret_cast(def.uvdata + face * 4); } void BlocksRenderer::render(const voxel* voxels, int atlas_size) { diff --git a/src/graphics/ImageData.cpp b/src/graphics/ImageData.cpp index caf88c26..bc45df56 100644 --- a/src/graphics/ImageData.cpp +++ b/src/graphics/ImageData.cpp @@ -11,6 +11,16 @@ inline int max(int a, int b) { return (a > b) ? a : b; } +ImageData::ImageData(ImageFormat format, uint width, uint height) + : format(format), width(width), height(height) { + switch (format) { + case ImageFormat::rgb888: data = new ubyte[width*height*3]{}; break; + case ImageFormat::rgba8888: data = new ubyte[width*height*4]{}; break; + default: + throw std::runtime_error("format is not supported"); + } +} + ImageData::ImageData(ImageFormat format, uint width, uint height, void* data) : format(format), width(width), height(height), data(data) { } @@ -70,6 +80,150 @@ void ImageData::flipY() { } } +void ImageData::blit(const ImageData* image, int x, int y) { + if (format != image->format) { + throw std::runtime_error("mismatching format"); + } + uint comps; + switch (format) { + case ImageFormat::rgb888: comps = 3; break; + case ImageFormat::rgba8888: comps = 4; break; + default: + throw std::runtime_error("only unsigned byte formats supported"); + } + ubyte* pixels = static_cast(data); + ubyte* source = static_cast(image->getData()); + uint srcwidth = image->getWidth(); + uint srcheight = image->getHeight(); + + for (uint srcy = max(0, -y); (int)srcy < min(srcheight, height-y); srcy++) { + for (uint srcx = max(0, -x); (int)srcx < min(srcwidth, width-x); srcx++) { + uint dstx = srcx + x; + uint dsty = srcy + y; + uint dstidx = (dsty * width + dstx) * comps; + uint srcidx = (srcy * srcwidth + srcx) * comps; + for (uint c = 0; c < comps; c++) { + pixels[dstidx + c] = source[srcidx + c]; + } + } + } +} + +/* Extrude rectangle zone border pixels out by 1 pixel. + Used to remove atlas texture border artifacts */ +void ImageData::extrude(int x, int y, int w, int h) { + uint comps; + switch (format) { + case ImageFormat::rgb888: comps = 3; break; + case ImageFormat::rgba8888: comps = 4; break; + default: + throw std::runtime_error("only unsigned byte formats supported"); + } + ubyte* pixels = static_cast(data); + + int rx = x + w - 1; + int ry = y + h - 1; + + // top-left pixel + if (x > 0 && (uint)x < width && y > 0 && (uint)y < height) { + uint srcidx = (y * width + x) * comps; + uint dstidx = ((y - 1) * width + x - 1) * comps; + for (uint c = 0; c < comps; c++) { + pixels[dstidx + c] = pixels[srcidx + c]; + } + } + + // top-right pixel + if (rx >= 0 && (uint)rx < width-1 && y > 0 && (uint)y < height) { + uint srcidx = (y * width + rx) * comps; + uint dstidx = ((y - 1) * width + rx + 1) * comps; + for (uint c = 0; c < comps; c++) { + pixels[dstidx + c] = pixels[srcidx + c]; + } + } + + // bottom-left pixel + if (x > 0 && (uint)x < width && ry >= 0 && (uint)ry < height-1) { + uint srcidx = (ry * width + x) * comps; + uint dstidx = ((ry + 1) * width + x - 1) * comps; + for (uint c = 0; c < comps; c++) { + pixels[dstidx + c] = pixels[srcidx + c]; + } + } + + // bottom-right pixel + if (rx >= 0 && (uint)rx < width-1 && ry >= 0 && (uint)ry < height-1) { + uint srcidx = (ry * width + rx) * comps; + uint dstidx = ((ry + 1) * width + rx + 1) * comps; + for (uint c = 0; c < comps; c++) { + pixels[dstidx + c] = pixels[srcidx + c]; + } + } + + // left border + if (x > 0 && (uint)x < width) { + for (uint ey = max(y, 0); (int)ey < y + h; ey++) { + uint srcidx = (ey * width + x) * comps; + uint dstidx = (ey * width + x - 1) * comps; + for (uint c = 0; c < comps; c++) { + pixels[dstidx + c] = pixels[srcidx + c]; + } + } + } + + // top border + if (y > 0 && (uint)y < height) { + for (uint ex = max(x, 0); (int)ex < x + w; ex++) { + uint srcidx = (y * width + ex) * comps; + uint dstidx = ((y-1) * width + ex) * comps; + for (uint c = 0; c < comps; c++) { + pixels[dstidx + c] = pixels[srcidx + c]; + } + } + } + + // right border + if (rx >= 0 && (uint)rx < width-1) { + for (uint ey = max(y, 0); (int)ey < y + h; ey++) { + uint srcidx = (ey * width + rx) * comps; + uint dstidx = (ey * width + rx + 1) * comps; + for (uint c = 0; c < comps; c++) { + pixels[dstidx + c] = pixels[srcidx + c]; + } + } + } + + // bottom border + if (ry >= 0 && (uint)ry < height-1) { + for (uint ex = max(x, 0); (int)ex < x + w; ex++) { + uint srcidx = (ry * width + ex) * comps; + uint dstidx = ((ry+1) * width + ex) * comps; + for (uint c = 0; c < comps; c++) { + pixels[dstidx + c] = pixels[srcidx + c]; + } + } + } +} + +void ImageData::fixAlphaColor() { + ubyte* pixels = static_cast(data); + + // Fixing black transparent pixels for Mip-Mapping + for (int ly = 0; ly < height-1; ly++) { + for (int lx = 0; lx < width-1; lx++) { + if (pixels[((ly) * width + lx) * 4 + 3]) { + for (int c = 0; c < 3; c++) { + int val = pixels[((ly) + + lx) * 4 + c]; + if (pixels[((ly) * width + lx + 1) * 4 + 3] == 0) + pixels[((ly) * width + lx + 1) * 4 + c] = val; + if (pixels[((ly + 1) * width + lx) * 4 + 3] == 0) + pixels[((ly + 1) * width + lx) * 4 + c] = val; + } + } + } + } +} + ImageData* add_atlas_margins(ImageData* image, int grid_size) { // RGBA is only supported assert(image->getFormat() == ImageFormat::rgba8888); @@ -95,7 +249,8 @@ ImageData* add_atlas_margins(ImageData* image, int grid_size) { int sy = max(min(ly, imgres-1), 0); int sx = max(min(lx, imgres-1), 0); for (int c = 0; c < 4; c++) - dstdata[((doy+ly) * dstwidth + dox + lx) * 4 + c] = srcdata[((soy+sy) * srcwidth + sox + sx) * 4 + c]; + dstdata[((doy+ly) * dstwidth + dox + lx) * 4 + c] = + srcdata[((soy+sy) * srcwidth + sox + sx) * 4 + c]; } } diff --git a/src/graphics/ImageData.h b/src/graphics/ImageData.h index b236293d..b7f73c01 100644 --- a/src/graphics/ImageData.h +++ b/src/graphics/ImageData.h @@ -14,12 +14,17 @@ class ImageData { uint height; void* data; public: + ImageData(ImageFormat format, uint width, uint height); ImageData(ImageFormat format, uint width, uint height, void* data); ~ImageData(); void flipX(); void flipY(); + void blit(const ImageData* image, int x, int y); + void extrude(int x, int y, int w, int h); + void fixAlphaColor(); + void* getData() const { return data; } diff --git a/src/maths/LMPacker.cpp b/src/maths/LMPacker.cpp new file mode 100644 index 00000000..eebca1e0 --- /dev/null +++ b/src/maths/LMPacker.cpp @@ -0,0 +1,149 @@ +#include "LMPacker.h" + +#include + +inline int getPackerScore(rectangle& rect) { + if (rect.width * rect.height > 100) + return rect.height * rect.height * 1000; + return (rect.width * rect.height * rect.height); +} + +LMPacker::LMPacker(const uint32_t sizes[], size_t length) { + for (unsigned int i = 0; i < length/2; i++) { + rectangle rect(0, 0, (int)sizes[i * 2], (int)sizes[i * 2 + 1]); + rects.push_back(rect); + } + sort(rects.begin(), rects.end(), [](rectangle a, rectangle b) { + return -getPackerScore(a) < -getPackerScore(b); + }); +} + +LMPacker::~LMPacker() { + if (matrix) { + for (unsigned int y = 0; y < (height >> mbit); y++) { + delete[] matrix[y]; + } + delete[] matrix; + } +} + +void LMPacker::cleanup() { + placed.clear(); +} + +bool LMPacker::build(uint32_t width, uint32_t height, + uint16_t extension, uint32_t mbit, uint32_t vstep) { + cleanup(); + this->mbit = mbit; + this->width = width; + this->height = height; + int mpix = 1 << mbit; + + const unsigned int mwidth = width >> mbit; + const unsigned int mheight = height >> mbit; + matrix = new rectangle**[mheight]; + for (unsigned int y = 0; y < mheight; y++) { + matrix[y] = new rectangle*[mwidth]; + for (unsigned int x = 0; x < mwidth; x++) { + matrix[y][x] = nullptr; + } + } + for (unsigned int i = 0; i < rects.size(); i++) { + rectangle& rect = rects[i]; + rect = rectangle(0, 0, rect.width, rect.height); + rect.width += extension * 2; + rect.height += extension * 2; + if (mpix > 1) { + if (rect.width % mpix > 0) { + rect.extX = mpix - (rect.width % mpix); + } + if (rect.height % mpix > 0) { + rect.extY = mpix - (rect.height % mpix); + } + } + rect.width += rect.extX; + rect.height += rect.extY; + } + bool built = true; + for (unsigned int i = 0; i < rects.size(); i++) { + rectangle* rect = &rects[i]; + if (!place(rect, vstep)) { + built = false; + break; + } + } + for (unsigned int i = 0; i < rects.size(); i++) { + rectangle& rect = rects[i]; + rect.x += extension; + rect.y += extension; + rect.width -= extension * 2 + rect.extX; + rect.height -= extension * 2 + rect.extY; + } + return built; +} + +inline rectangle* findCollision(rectangle*** matrix, int x, int y, int w, int h) { + for (int row = y; row < y+h; row++) { + for (int col = x; col < x+w; col++) { + rectangle* rect = matrix[row][col]; + if (rect) { + return rect; + } + } + } + return nullptr; +} + +inline void fill(rectangle*** matrix, rectangle* rect, int x, int y, int w, int h) { + for (int row = y; row < y+h; row++) { + for (int col = x; col < x+w; col++) { + matrix[row][col] = rect; + } + } +} + +bool LMPacker::place(rectangle* rectptr, uint32_t vstep) { + rectangle& rect = *rectptr; + const unsigned int rw = rect.width >> mbit; + const unsigned int rh = rect.height >> mbit; + if (vstep > 1) { + vstep = (vstep > rh ? vstep : rh); + } + const unsigned int mwidth = width >> mbit; + const unsigned int mheight = height >> mbit; + for (unsigned int y = 0; y + rh < mheight; y += vstep) { + rectangle** line = matrix[y]; + bool skiplines = true; + rectangle** lower = matrix[y + rh - 1]; + for (unsigned int x = 0; x + rw < mwidth; x++) { + rectangle* prect = line[x]; + if (prect) { + x = (prect->x >> mbit) + (prect->width >> mbit) - 1; + } else { + if (skiplines) { + unsigned int lfree = 0; + while (lfree + x < mwidth && !lower[x + lfree] && lfree < rw) { + lfree++; + } + if (lfree >= rw) + skiplines = false; + } + prect = findCollision(matrix, x, y, rw, rh); + if (prect) { + x = (prect->x >> mbit) + (prect->width >> mbit) - 1; + continue; + } + fill(matrix, rectptr, x, y, rw, rh); + rectptr->x = x << mbit; + rectptr->y = y << mbit; + placed.push_back(rectptr); + return true; + } + } + if (skiplines) { + y += rh - vstep; + } + } + return false; +} + diff --git a/src/maths/LMPacker.h b/src/maths/LMPacker.h new file mode 100644 index 00000000..c280a85f --- /dev/null +++ b/src/maths/LMPacker.h @@ -0,0 +1,52 @@ +/* + C++ LMPacker port + https://github.com/MihailRis/LMPacker +*/ +#ifndef LMPACKER_H_ +#define LMPACKER_H_ + +#include +#include +#include + +struct rectangle { + int x; + int y; + int width; + int height; + int extX = 0; + int extY = 0; + + rectangle(int x, int y, int width, int height) + : x(x), y(y), width(width), height(height){ + } +}; + +class LMPacker { + std::vector rects; + std::vector placed; + uint32_t width = 0; + uint32_t height = 0; + rectangle*** matrix = nullptr; + uint32_t mbit = 0; + + void cleanup(); + bool place(rectangle* rect, uint32_t vstep); +public: + LMPacker(const uint32_t sizes[], size_t length); + virtual ~LMPacker(); + + bool buildCompact(uint32_t width, uint32_t height, uint16_t extension) { + return build(width, height, extension, 0, 1); + } + bool buildFast(uint32_t width, uint32_t height, uint16_t extension) { + return build(width, height, extension, 1, 2); + } + bool build(uint32_t width, uint32_t height, uint16_t extension, uint32_t mbit, uint32_t vstep); + + std::vector getResult() { + return rects; + } +}; + +#endif /* LMPACKER_H_ */ \ No newline at end of file diff --git a/src/voxel_engine.cpp b/src/voxel_engine.cpp index 509fff61..808e125a 100644 --- a/src/voxel_engine.cpp +++ b/src/voxel_engine.cpp @@ -13,6 +13,10 @@ #include "files/settings_io.h" #include "content/Content.h" +#include "coders/png.h" +#include "graphics/Atlas.h" +#include "graphics/ImageData.h" + int main() { platform::configure_encoding(); ContentBuilder contentBuilder; diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index 880facf3..3836d0f6 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -1,6 +1,6 @@ #include "Block.h" -Block::Block(std::string name, int texture) : name(name), +Block::Block(std::string name, std::string texture) : name(name), textureFaces{texture,texture,texture,texture,texture,texture}, emission{0,0,0}{ } diff --git a/src/voxels/Block.h b/src/voxels/Block.h index bd177e74..f579ff1d 100644 --- a/src/voxels/Block.h +++ b/src/voxels/Block.h @@ -18,8 +18,8 @@ class Block { public: std::string const name; unsigned int id; - // 0 1 2 3 4 5 - int textureFaces[6]; // -x,x, -y,y, -z,z + // 0 1 2 3 4 5 + std::string textureFaces[6]; // -x,x, -y,y, -z,z unsigned char emission[3]; unsigned char drawGroup = 0; BlockModel model = BlockModel::block; @@ -31,7 +31,9 @@ public: bool rotatable = false; float hitboxScale = 1; - Block(std::string name, int texture); + float uvdata[4*6]; + + Block(std::string name, std::string texture); }; #endif /* VOXELS_BLOCK_H_ */