// Find a place to pack a bitmap into a larger bitmap BinPackNode *BinPack(BMSize BitmapWidth, BMSize BitmapHeight, BinPackNode *ParentNode) { // This function is copied almost exactly from: http://www.blackpawn.com/texts/lightmaps/ if(ParentNode->Children[0] != nullptr || ParentNode->Children[1] != nullptr) { // Try inserting into first child BinPackNode *newNode = BinPack(BitmapWidth, BitmapHeight, ParentNode->Children[0].get()); if(newNode != nullptr) return newNode; // No room in first child, try inserting into second return BinPack(BitmapWidth, BitmapHeight, ParentNode->Children[1].get()); } else { // Check if there is already a glyph here if(ParentNode->Filled) return nullptr; // Check if the glyph is too big to fit in this node if(ParentNode->Rect.z < BitmapWidth || ParentNode->Rect.w < BitmapHeight) return nullptr; // Check if the glyph fits perfectly in this node if(ParentNode->Rect.z == BitmapWidth && ParentNode->Rect.w == BitmapHeight) { ParentNode->Filled = true; return ParentNode; } // Node is larger than the glyph, so split the node into two ParentNode->Children[0].reset(new BinPackNode()); ParentNode->Children[1].reset(new BinPackNode()); // Decide which way to split int SpaceWidth = ParentNode->Rect.z - BitmapWidth; int SpaceHeight = ParentNode->Rect.w - BitmapHeight; if(SpaceWidth > SpaceHeight) { ParentNode->Children[0]->Rect = vec4(ParentNode->Rect.x, ParentNode->Rect.y, BitmapWidth, ParentNode->Rect.w); ParentNode->Children[1]->Rect = vec4(ParentNode->Rect.x + BitmapWidth, ParentNode->Rect.y, SpaceWidth, ParentNode->Rect.w); } else { ParentNode->Children[0]->Rect = vec4(ParentNode->Rect.x, ParentNode->Rect.y, ParentNode->Rect.z, BitmapHeight); ParentNode->Children[1]->Rect = vec4(ParentNode->Rect.x, ParentNode->Rect.y + BitmapHeight, ParentNode->Rect.z, SpaceHeight); } // Insert into first child return BinPack(BitmapWidth, BitmapHeight, ParentNode->Children[0].get()); } }
bool Font::CreateAtlas() { unsigned int totalPixels = 0; for (FontSymbols::const_iterator it = this->symbols.cbegin(), end = this->symbols.cend(); it != end; ++it) { std::shared_ptr<FontSymbol> glyph = it->second; totalPixels += glyph->GetPixelCount(); } unsigned int textureWidth = 1; unsigned int textureHeight = 1; while (textureWidth * textureHeight < totalPixels) { if (textureWidth < textureHeight) { textureWidth *= 2; } else { textureHeight *= 2; } if (textureWidth > MaxTextureDimension || textureHeight > MaxTextureDimension) { LogError("Font atlas became too large."); return false; } } std::vector<std::shared_ptr<FontSymbol>> sortedSymbols; for (FontSymbols::const_iterator it = this->symbols.cbegin(), end = this->symbols.cend(); it != end; ++it) { sortedSymbols.push_back(it->second); } std::sort(sortedSymbols.begin(), sortedSymbols.end(), [](std::shared_ptr<FontSymbol> &glyph1, std::shared_ptr<FontSymbol> &glyph2) -> bool { return glyph1->GetPixelCount() > glyph2->GetPixelCount(); } ); GlyphTreeNode::GetAllocator().Init((this->symbols.size() + 1) * 2); if (!BinPack(textureWidth, textureHeight)) { // TODO: We shouldn't fail like this because binary packing the glyphs might fail if for example // texture is not big enough to fit all glyphs because of too many unused gaps. In such case we should // continue looping and increase textureWidth & textureHeight until BinPack succeeds. GlyphTreeNode::GetAllocator().Release(); LogError("Font::BinPack has failed."); return false; } GlyphTreeNode::GetAllocator().Release(); unsigned char *textureBuffer = new unsigned char[textureWidth * textureHeight]; for (FontSymbols::const_iterator it = this->symbols.cbegin(), end = this->symbols.cend(); it != end; ++it) { std::shared_ptr<FontSymbol> symbol = it->second; symbol->RenderToBuffer(textureBuffer, textureWidth, textureHeight); symbol->GlyphLoaded(); } glGenTextures(1, &this->glTexture); glBindTexture(GL_TEXTURE_2D, this->glTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, textureWidth, textureHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, textureBuffer); GL_CHECK_ERROR(); delete[] textureBuffer; this->atlas = std::shared_ptr<FontSymbol>(TG_NEW FontSymbol); this->atlas->SetXY(0, 0); this->atlas->SetSize(textureWidth, textureHeight); this->atlas->InitTextureCoords(textureWidth, textureHeight); return true; }