atlasbook.cpp

00001 /***************************************************************************
00002  *   Copyright (C) 2005-2011 by the FIFE team                              *
00003  *   http://www.fifengine.net                                              *
00004  *   This file is part of FIFE.                                            *
00005  *                                                                         *
00006  *   FIFE is free software; you can redistribute it and/or                 *
00007  *   modify it under the terms of the GNU Lesser General Public            *
00008  *   License as published by the Free Software Foundation; either          *
00009  *   version 2.1 of the License, or (at your option) any later version.    *
00010  *                                                                         *
00011  *   This library is distributed in the hope that it will be useful,       *
00012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
00014  *   Lesser General Public License for more details.                       *
00015  *                                                                         *
00016  *   You should have received a copy of the GNU Lesser General Public      *
00017  *   License along with this library; if not, write to the                 *
00018  *   Free Software Foundation, Inc.,                                       *
00019  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
00020  ***************************************************************************/
00021  
00022 // FIFE includes
00023 // These includes are split up in two parts, separated by one empty line
00024 // First block: files included from the FIFE root src directory
00025 // Second block: files included from the same folder
00026 #include "util/base/exception.h"
00027 
00028 #include "atlasbook.h"
00029 
00030 namespace FIFE {
00031     AtlasBlock AtlasBlock::intersects(AtlasBlock const& rect) const {
00032         AtlasBlock ret;
00033 
00034         ret.left = std::max(rect.left, left);
00035         ret.right = std::min(rect.right, right);
00036         ret.top = std::max(rect.top, top);
00037         ret.bottom = std::min(rect.bottom, bottom);
00038 
00039         if(ret.left > ret.right || ret.top > ret.bottom) {
00040             ret.setTrivial();
00041         }
00042 
00043         return ret;
00044     }
00045 
00046     void AtlasBlock::merge(AtlasBlock const& rect) {
00047         left = std::min(rect.left, left);
00048         right = std::max(rect.right, right);
00049         top = std::min(rect.top, top);
00050         bottom = std::max(rect.bottom, bottom);
00051     }
00052     
00053     AtlasBlock* AtlasPage::getBlock(uint32_t width, uint32_t height) {
00054         if(static_cast<int32_t>(width*height*pixelSize) > freePixels) {
00055             return 0;
00056         }
00057 
00058         blocks.push_back(AtlasBlock(Rect(), 0));
00059         AtlasBlock* newBlock = &blocks[blocks.size() - 1];
00060 
00061         for(uint32_t v = 0; (v+1)*height <= this->height; ++v) {
00062             newBlock->top = v * height;
00063             newBlock->bottom = (v+1) * height;
00064 
00065             for(uint32_t u = 0; (u+1)*width <= this->width; ++u) {
00066 
00067                 newBlock->left = u * width;
00068                 newBlock->right = (u+1) * width;
00069 
00070                 AtlasBlock const* intersection = intersects(newBlock);
00071                 if(!intersection) {
00072                     freePixels -= width*height*pixelSize;
00073                     assert(freePixels >= 0);
00074 
00075                     // try to squeeze a little bit (horizontal)
00076                     if(newBlock->left > 0) {
00077                         AtlasBlock squeezed(*newBlock);
00078 
00079                         --squeezed.left;
00080                         --squeezed.right;
00081 
00082                         if(!(intersection = intersects(&squeezed))) {
00083                             ++squeezed.left;
00084                             ++squeezed.right;
00085                             int blockWidth = newBlock->getWidth();
00086 
00087                             // binary search
00088                             for(int i = 0, div = 2; i < 4; ++i) {
00089                                 squeezed.left -= blockWidth / div;
00090                                 squeezed.right -= blockWidth / div;
00091 
00092                                 if((intersection = intersects(&squeezed))) {
00093                                     squeezed.left += blockWidth / div;
00094                                     squeezed.right += blockWidth / div;
00095                                 }
00096                                 div <<= 1;
00097                             }
00098 
00099                             // linear search
00100                             while(!(intersection = intersects(&squeezed)) && squeezed.left > 0) {
00101                                 --squeezed.left;
00102                                 --squeezed.right;
00103                             }
00104 
00105                             newBlock->left = squeezed.left + 1;
00106                             newBlock->right = squeezed.right + 1;
00107                         }
00108                     }
00109 
00110                     // try to squeeze a little bit (vertical)
00111                     if(newBlock->top > 0) {
00112                         AtlasBlock squeezed(*newBlock);
00113 
00114                         --squeezed.top;
00115                         --squeezed.bottom;
00116 
00117                         if(!(intersection = intersects(&squeezed))) {
00118                             ++squeezed.top;
00119                             ++squeezed.bottom;
00120                             int blockHeight = newBlock->getHeight();
00121 
00122                             // binary search
00123                             for(int i = 0, div = 2; i < 4; ++i) {
00124                                 squeezed.top -= blockHeight / div;
00125                                 squeezed.bottom -= blockHeight / div;
00126 
00127                                 if((intersection = intersects(&squeezed))) {
00128                                     squeezed.top += blockHeight / div;
00129                                     squeezed.bottom += blockHeight / div;
00130                                 }
00131                                 div <<= 1;
00132                             }
00133 
00134                             // linear search
00135                             while(!(intersection = intersects(&squeezed)) && squeezed.top > 0) {
00136                                 --squeezed.top;
00137                                 --squeezed.bottom;
00138                             }
00139 
00140                             newBlock->top = squeezed.top + 1;
00141                             newBlock->bottom = squeezed.bottom + 1;
00142                         }
00143                     }
00144 
00145                     newBlock->page = this->page;
00146                     return newBlock;
00147                 }
00148             }
00149         }
00150         // couldn't find suitable place for a new block
00151         blocks.pop_back();
00152         return 0;
00153     }
00154 
00155     void AtlasPage::shrink(bool pot) {
00156         AtlasBlock boundaryBox(Rect(), 0);
00157         for(Blocks::iterator block = blocks.begin(); block != blocks.end(); ++block) {
00158             boundaryBox.merge(*block);
00159         }
00160 
00161         assert(boundaryBox.left == 0);
00162         assert(boundaryBox.top == 0);
00163 
00164         if(pot) {
00165             uint32_t bwidth = boundaryBox.getWidth();
00166             uint32_t bheight = boundaryBox.getHeight();
00167 
00168             if(bwidth < width) {
00169                 // look for next power of 2 for width
00170                 uint32_t powof2 = 1;
00171                 while(powof2 < bwidth) powof2 <<= 1;
00172                 width = std::min(powof2, width);
00173             }
00174 
00175             if(bheight < height) {
00176                 // look for next power of 2 for width
00177                 uint32_t powof2 = 1;
00178                 while(powof2 < bheight) powof2 <<= 1;
00179                 height = std::min(powof2, height);
00180             }
00181 
00182         } else {
00183             width = boundaryBox.getWidth();
00184             height = boundaryBox.getHeight();
00185         }
00186     }   
00187     
00188     AtlasBlock const* AtlasPage::intersects(AtlasBlock const* block) const {
00189         for(size_t b = 0; b < blocks.size() - 1; ++b) {
00190             if(!blocks[b].intersects(*block).isTrivial())
00191                 return block;
00192         }
00193         // no intersection
00194         return 0;
00195     }   
00196     
00197     AtlasBlock* AtlasBook::getBlock(uint32_t width, uint32_t height) {
00198         for(Pages::iterator page = pages.begin(); page != pages.end(); ++page) {
00199             AtlasBlock* block = page->getBlock(width, height);
00200             if(block) {
00201                 return block;
00202             }
00203         }
00204         return extendCache(width, height)->getBlock(width, height);
00205     }
00206 
00207     AtlasPage* AtlasBook::extendCache(uint32_t minPageWidth, uint32_t minPageHeight) {
00208         if(minPageWidth > pageWidth ||
00209            minPageHeight > pageHeight) {
00210             throw Exception("Texture is too big for this atlas.");
00211         }
00212 
00213         assert(minPageWidth <= pageWidth);
00214         assert(minPageHeight <= pageHeight);
00215 
00216         pages.push_back(AtlasPage(pageWidth, pageHeight, pixelSize, pages.size()));
00217         return &pages[pages.size()-1];
00218     }
00219 
00220     void AtlasBook::shrink(bool pot) {
00221         for(Pages::iterator page = pages.begin(); page != pages.end(); ++page) {
00222             page->shrink(pot);
00223         }
00224     }   
00225 }