add glyph atlas class
This commit is contained in:
53
include/llmr/geometry/glyph_atlas.hpp
Normal file
53
include/llmr/geometry/glyph_atlas.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef LLMR_GEOMETRY_GLYPH_ATLAS
|
||||
#define LLMR_GEOMETRY_GLYPH_ATLAS
|
||||
|
||||
#include <llmr/geometry/binpack.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Glyph {
|
||||
public:
|
||||
uint32_t id = 0;
|
||||
std::string bitmap;
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
};
|
||||
|
||||
class GlyphAtlas {
|
||||
public:
|
||||
|
||||
private:
|
||||
struct GlyphValue {
|
||||
GlyphValue(const Rect<uint16_t>& rect, uint64_t id)
|
||||
: rect(rect), ids({ id }) {}
|
||||
Rect<uint16_t> rect;
|
||||
std::set<uint64_t> ids;
|
||||
};
|
||||
|
||||
public:
|
||||
GlyphAtlas(uint16_t width, uint16_t height);
|
||||
~GlyphAtlas();
|
||||
|
||||
|
||||
Rect<uint16_t> addGlyph(uint64_t tile_id, const std::string& face_name,
|
||||
const Glyph& glyph, uint8_t buffer);
|
||||
void removeGlyphs(uint64_t tile_id);
|
||||
void bind();
|
||||
|
||||
private:
|
||||
const uint16_t width = 0;
|
||||
const uint16_t height = 0;
|
||||
BinPack<uint16_t> bin;
|
||||
std::map<std::string, std::map<uint32_t, GlyphValue>> index;
|
||||
char *const data = NULL;
|
||||
bool dirty = true;
|
||||
uint32_t texture = 0;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
130
src/geometry/glyph_atlas.cpp
Normal file
130
src/geometry/glyph_atlas.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
GlyphAtlas::GlyphAtlas(uint16_t width, uint16_t height)
|
||||
: width(width),
|
||||
height(height),
|
||||
bin(width, height),
|
||||
data(new char[width *height]) {
|
||||
}
|
||||
|
||||
GlyphAtlas::~GlyphAtlas() {
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
Rect<uint16_t> GlyphAtlas::addGlyph(uint64_t tile_id, const std::string& face_name,
|
||||
const Glyph& glyph, uint8_t buffer) {
|
||||
|
||||
std::map<uint32_t, GlyphValue>& face = index[face_name];
|
||||
std::map<uint32_t, GlyphValue>::iterator it = face.find(glyph.id);
|
||||
|
||||
// The glyph is already in this texture.
|
||||
if (it != face.end()) {
|
||||
GlyphValue& value = it->second;
|
||||
value.ids.insert(tile_id);
|
||||
return value.rect;
|
||||
}
|
||||
|
||||
// The glyph bitmap has zero width.
|
||||
if (!glyph.width) {
|
||||
return { 0, 0, 0, 0 };
|
||||
}
|
||||
|
||||
uint16_t buffered_width = glyph.width + buffer * 2;
|
||||
uint16_t buffered_height = glyph.height + buffer * 2;
|
||||
|
||||
// Add a 1px border around every image.
|
||||
uint16_t pack_width = buffered_width;
|
||||
uint16_t pack_height = buffered_height;
|
||||
|
||||
// Increase to next number divisible by 4, but at least 1.
|
||||
// This is so we can scale down the texture coordinates and pack them
|
||||
// into 2 bytes rather than 4 bytes.
|
||||
pack_width += (4 - pack_width % 4);
|
||||
pack_height += (4 - pack_height % 4);
|
||||
|
||||
Rect<uint16_t> rect = bin.allocate(pack_width, pack_height);
|
||||
if (rect.w == 0) {
|
||||
fprintf(stderr, "glyph bitmap overflow");
|
||||
return rect;
|
||||
}
|
||||
|
||||
assert(rect.x + rect.w <= width);
|
||||
assert(rect.y + rect.h <= height);
|
||||
|
||||
face.emplace(glyph.id, GlyphValue { rect, tile_id });
|
||||
|
||||
// Copy the bitmap
|
||||
char *target = data;
|
||||
const char *source = glyph.bitmap.data();
|
||||
for (uint32_t y = 0; y < buffered_height; y++) {
|
||||
uint32_t y1 = width * (rect.y + y) + rect.x;
|
||||
uint32_t y2 = buffered_width * y;
|
||||
for (uint32_t x = 0; x < buffered_width; x++) {
|
||||
target[y1 + x] = source[y2 + x];
|
||||
}
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
void GlyphAtlas::removeGlyphs(uint64_t tile_id) {
|
||||
for (auto& faces : index) {
|
||||
std::map<uint32_t, GlyphValue>& face = faces.second;
|
||||
for (auto it = face.begin(); it != face.end(); /* we advance in the body */) {
|
||||
GlyphValue& value = it->second;
|
||||
value.ids.erase(tile_id);
|
||||
|
||||
if (!value.ids.size()) {
|
||||
const Rect<uint16_t>& rect = value.rect;
|
||||
|
||||
// Clear out the bitmap.
|
||||
char *target = data;
|
||||
for (uint32_t y = 0; y < rect.h; y++) {
|
||||
uint32_t y1 = width * (rect.y + y) + rect.x;
|
||||
for (uint32_t x = 0; x < rect.w; x++) {
|
||||
target[y1 + x] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
|
||||
bin.release(rect);
|
||||
|
||||
// Make sure to post-increment the iterator: This will return the
|
||||
// current iterator, but will go to the next position before we
|
||||
// erase the element from the map. That way, the iterator stays
|
||||
// valid.
|
||||
face.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GlyphAtlas::bind() {
|
||||
if (!texture) {
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data);
|
||||
dirty = false;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user