/* Copyright (c) 2013, christopher stones < chris.stones@zoho.com > All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * BinPack2D is a 2 dimensional, multi-bin, bin-packer. ( Texture Atlas Array! ) * It supports an arbitrary number of bins, at arbitrary sizes. * rectangles can be added one at a time, chunks at a time, or all at once. * rectangles that dont fit are reported back. * Data can be associated to rectangles before processing via a template, and recalled after processing. * * There is no documentation, See ExampleProgram() below for a taste. * * Instead of tracking 'free rectangles' like other solutions I've found online, * this algorithm tracks free 'top lefts', keeps them sorted by closest to origin, and puts new rectangles into * the first free top left that doesnt collide. Consuming a top left creates 2 new top lefts (x+w,y) and (x,y+h). * If a rectangle doesnt fit into a bin, before condisering the next bin, the current bin is re-tried with the rectangle rotated. * This SOMTIMES helps... but not always.. i might disable this in future !? * * This Header was origonally part of my rh_texture_packer program. * A program I wrote to take advantage of my nexus-7's GL_EXT_texture_array extension. * I wanted to be able to render out whole scenes with a single glDraw* * blah blah blah... */ /** ***** EXAMPLE CODE ************************************** // Your data - whatever you want to associate with 'rectangle' class MyContent { public: std::string str; MyContent() : str("default string") {} MyContent(const std::string &str) : str(str) {} }; int ExampleProgram() { srandom(0x69); // Create some 'content' to work on. BinPack2D::ContentAccumulator inputContent; for(int i=0;i<20;i++) { // random size for this content int width = ((random() % 32)+1) * ((random() % 10)+1); int height = ((random() % 32)+1) * ((random() % 10)+1); // whatever data you want to associate with this content std::stringstream ss; ss << "box " << i; MyContent mycontent( ss.str().c_str() ); // Add it inputContent += BinPack2D::Content(mycontent, BinPack2D::Coord(), BinPack2D::Size(width, height), false ); } // Sort the input content by size... usually packs better. inputContent.Sort(); // Create some bins! ( 2 bins, 128x128 in this example ) BinPack2D::CanvasArray canvasArray = BinPack2D::UniformCanvasArrayBuilder(128,128,2).Build(); // A place to store content that didnt fit into the canvas array. BinPack2D::ContentAccumulator remainder; // try to pack content into the bins. bool success = canvasArray.Place( inputContent, remainder ); // A place to store packed content. BinPack2D::ContentAccumulator outputContent; // Read all placed content. canvasArray.CollectContent( outputContent ); // parse output. typedef BinPack2D::Content::Vector::iterator binpack2d_iterator; printf("PLACED:\n"); for( binpack2d_iterator itor = outputContent.Get().begin(); itor != outputContent.Get().end(); itor++ ) { const BinPack2D::Content &content = *itor; // retreive your data. const MyContent &myContent = content.content; printf("\t%9s of size %3dx%3d at position %3d,%3d,%2d rotated=%s\n", myContent.str.c_str(), content.size.w, content.size.h, content.coord.x, content.coord.y, content.coord.z, (content.rotated ? "yes":" no")); } printf("NOT PLACED:\n"); for( binpack2d_iterator itor = remainder.Get().begin(); itor != remainder.Get().end(); itor++ ) { const BinPack2D::Content &content = *itor; const MyContent &myContent = content.content; printf("\t%9s of size %3dx%3d\n", myContent.str.c_str(), content.size.w, content.size.h); } exit(0); } */ #pragma once #include #include #include #include #include #include namespace BinPack2D { class Size { public: /*const*/ int w; /*const*/ int h; Size(int w, int h) : w(w), h(h) {} bool operator < ( const Size &that ) const { if(this->w != that.w) return this->w < that.w; if(this->h != that.h) return this->h < that.h; } }; class Coord { public: typedef std::vector Vector; typedef std::list List; /*const*/ int x; /*const*/ int y; /*const*/ int z; Coord() : x(0), y(0), z(0) {} Coord(int x, int y) : x(x), y(y), z(0) {} Coord(int x, int y, int z) : x(x), y(y), z(z) {} bool operator < ( const Coord &that ) const { if(this->x != that.x) return this->x < that.x; if(this->y != that.y) return this->y < that.y; if(this->z != that.z) return this->z < that.z; } }; template class Content { public: typedef std::vector > Vector; /*const*/ bool rotated; /*const*/ Coord coord; /*const*/ Size size; /*const*/ _T content; Content( const Content<_T> &src ) : rotated(src.rotated), coord(src.coord), size(src.size), content(src.content) {} Content( const _T &content, const Coord &coord, const Size &size, bool rotated ) : content(content), coord(coord), size(size), rotated(rotated) {} void Rotate() { rotated = !rotated; size = Size( size.h, size.w ); } bool intersects(const Content<_T> &that) const { if(this->coord.x >= (that.coord.x + that.size.w)) return false; if(this->coord.y >= (that.coord.y + that.size.h)) return false; if(that.coord.x >= (this->coord.x + this->size.w)) return false; if(that.coord.y >= (this->coord.y + this->size.h)) return false; return true; } }; template class Canvas { Coord::List topLefts; typename Content<_T>::Vector contentVector; bool needToSort; public: typedef Canvas<_T> CanvasT; typedef typename std::vector Vector; static bool Place( Vector &canvasVector, const typename Content<_T>::Vector &contentVector, typename Content<_T>::Vector &remainder ) { typename Content<_T>::Vector todo = contentVector; for( typename Vector::iterator itor = canvasVector.begin(); itor != canvasVector.end(); itor++ ) { Canvas <_T> &canvas = *itor; remainder.clear(); canvas.Place(todo, remainder); todo = remainder; } if(remainder.size()==0) return true; return false; } static bool Place( Vector &canvasVector, const typename Content<_T>::Vector &contentVector ) { typename Content<_T>::Vector remainder; return Place( canvasVector, contentVector, remainder ); } static bool Place( Vector &canvasVector, const Content<_T> &content ) { typename Content<_T>::Vector contentVector(1, content); return Place( canvasVector, contentVector ); } const int w; const int h; Canvas(int w, int h) : needToSort(false), w(w), h(h) { topLefts.push_back( Coord(0,0) ); } bool HasContent() const { return ( contentVector.size() > 0) ; } const typename Content<_T>::Vector &GetContents( ) const { return contentVector; } bool operator < ( const Canvas &that ) const { if(this->w != that.w) return this->w < that.w; if(this->h != that.h) return this->h < that.h; } bool Place(const typename Content<_T>::Vector &contentVector, typename Content<_T>::Vector &remainder) { bool placedAll = true; for( typename Content<_T>::Vector::const_iterator itor = contentVector.begin(); itor != contentVector.end(); itor++ ) { const Content<_T> & content = *itor; if( Place( content ) == false ) { placedAll = false; remainder.push_back( content ); } } return placedAll; } bool Place(Content<_T> content) { Sort(); for( Coord::List::iterator itor = topLefts.begin(); itor != topLefts.end(); itor++ ) { content.coord = *itor; if( Fits( content ) ) { Use( content ); topLefts.erase( itor ); return true; } } return false; // yvt: BitmapAtlasGenerator doesn't support rotated contents. // EXPERIMENTAL - TRY ROTATED? content.Rotate(); for( Coord::List::iterator itor = topLefts.begin(); itor != topLefts.end(); itor++ ) { content.coord = *itor; if( Fits( content ) ) { Use( content ); topLefts.erase( itor ); return true; } } //////////////////////////////// return false; } private: bool Fits( const Content<_T> &content ) const { if( (content.coord.x + content.size.w) > w ) return false; if( (content.coord.y + content.size.h) > h ) return false; for( typename Content<_T>::Vector::const_iterator itor = contentVector.begin(); itor != contentVector.end(); itor++ ) if( content.intersects( *itor ) ) return false; return true; } bool Use(const Content<_T> &content) { const Size &size = content.size; const Coord &coord = content.coord; topLefts.push_front ( Coord( coord.x + size.w, coord.y ) ); topLefts.push_back ( Coord( coord.x , coord.y + size.h ) ); contentVector.push_back( content ); needToSort = true; return true; } private: struct TopToBottomLeftToRightSort { bool operator()(const Coord &a, const Coord &b) const { return sqrtf( a.x * a.x + a.y * a.y ) < sqrtf( b.x * b.x + b.y * b.y ); if(a.y != b.y) return a.y < b.y; return a.x < b.x; } }; public: void Sort() { if(!needToSort) return; topLefts.sort(TopToBottomLeftToRightSort()); needToSort = false; } }; template class ContentAccumulator { typename Content<_T>::Vector contentVector; public: ContentAccumulator() {} const typename Content<_T>::Vector &Get() const { return contentVector; } typename Content<_T>::Vector &Get() { return contentVector; } ContentAccumulator<_T>& operator += ( const Content<_T> & content ) { contentVector.push_back( content ); return *this; } ContentAccumulator<_T>& operator += ( const typename Content<_T>::Vector & content ) { contentVector.insert( contentVector.end(), content.begin(), content.end() ); return *this; } ContentAccumulator<_T> operator + ( const Content<_T> & content ) { ContentAccumulator<_T> temp = *this; temp += content; return temp; } ContentAccumulator<_T> operator + ( const typename Content<_T>::Vector & content ) { ContentAccumulator<_T> temp = *this; temp += content; return temp; } private: struct GreatestWidthThenGreatestHeightSort { bool operator()(const Content<_T> &a, const Content<_T> &b) const { const Size &sa = a.size; const Size &sb = b.size; // return( sa.w * sa.h > sb.w * sb.h ); if(sa.w != sb.w) return sa.w > sb.w; return sa.h > sb.h; } }; struct MakeHorizontal { Content<_T> operator()( const Content<_T> &elem) { if(elem.size.h > elem.size.w) { Content<_T> r = elem; r.size.w = elem.size.h; r.size.h = elem.size.w; r.rotated = !elem.rotated; return r; } return elem; } }; public: void Sort() { // if(allow_rotation) // std::transform(contentVector.begin(), contentVector.end(), contentVector.begin(), MakeHorizontal()); std::sort( contentVector.begin(), contentVector.end(), GreatestWidthThenGreatestHeightSort() ); } }; template class UniformCanvasArrayBuilder { int w; int h; int d; public: UniformCanvasArrayBuilder( int w, int h, int d ) : w(w), h(h), d(d) {} typename Canvas<_T>::Vector Build() { return typename Canvas<_T>::Vector(d, Canvas<_T>(w, h) ); } }; template class CanvasArray { typename Canvas<_T>::Vector canvasArray; public: CanvasArray( const typename Canvas<_T>::Vector &canvasArray ) : canvasArray( canvasArray ) {} bool Place(const typename Content<_T>::Vector &contentVector, typename Content<_T>::Vector &remainder) { return Canvas<_T>::Place( canvasArray, contentVector, remainder ); } bool Place(const ContentAccumulator<_T> &content, ContentAccumulator<_T> &remainder) { return Place( content.Get(), remainder.Get() ); } bool Place(const typename Content<_T>::Vector &contentVector) { return Canvas<_T>::Place( canvasArray, contentVector ); } bool Place(const ContentAccumulator<_T> &content) { return Place( content.Get() ); } bool CollectContent( typename Content<_T>::Vector &contentVector ) const { int z = 0; for( typename Canvas<_T>::Vector::const_iterator itor = canvasArray.begin(); itor != canvasArray.end(); itor++ ) { const typename Content<_T>::Vector &contents = itor->GetContents(); for( typename Content<_T>::Vector::const_iterator itor = contents.begin(); itor != contents.end(); itor++ ) { Content<_T> content = *itor; content.coord.z = z; contentVector.push_back( content ); } z++; } return true; } bool CollectContent( ContentAccumulator<_T> &content) const { return CollectContent( content.Get() ); } }; } /*** BinPack2D ***/