Path: blob/master/src/engine/packingalgorithms.py
254 views
# Packing Algorithm based on https://github.com/jakesgordon/bin-packing/blob/master/js/packer.growing.js1# converted to python2# By (Jake Gordon)[https://github.com/jakesgordon]3class GrowingPacker:4def __init__(self):5self.root = None67def fit(self, blocks):8num_blocks = len(blocks)9w = blocks[0].get("w", 0) if num_blocks > 0 else 010h = blocks[0].get("h", 0) if num_blocks > 0 else 011self.root = { "x":0, "y":0, "w":w, "h":h }12for block in blocks:13node = self.find_node(self.root, block.get("w", 0), block.get("h", 0))14if node:15block["fit"] = self.split_node(node, block.get("w", 0), block.get("h", 0))16else:17block["fit"] = self.grow_node(block.get("w", 0), block.get("h", 0))1819def find_node(self, root, w, h):20if root.get("used"):21return self.find_node(root.get("right"), w, h) or self.find_node(root.get("down"), w, h)22elif w <= root.get("w", 0) and h <= root.get("h", 0):23return root24else:25return None2627def split_node(self, node, w, h):28node["used"] = True29node['down'] = { "x": node.get("x"), "y": node.get("y") + h, "w": node.get("w"), "h":node.get("h") - h }30node['right'] = { "x": node.get("x") + w, "y": node.get("y"), "w": node.get("w") - w, "h": h }31return node3233def grow_node(self, w, h):34canGrowDown = (w <= self.root.get("w"))35canGrowRight = (h <= self.root.get("h"))3637shouldGrowRight = canGrowRight and (self.root.get("h") >= (self.root.get("w") + w))38shouldGrowDown = canGrowDown and (self.root.get("w") >= (self.root.get("h") + h))3940if shouldGrowRight:41return self.grow_right(w, h)42elif shouldGrowDown:43return self.grow_down(w, h)44elif canGrowRight:45return self.grow_right(w, h)46elif canGrowDown:47return self.grow_down(w, h)48else:49return None5051def grow_right(self, w, h):52self.root = {53"used": True,54"x":0,55"y":0,56"w":self.root.get("w")+w,57"h":self.root.get("h"),58"down":self.root,59"right": { "x": self.root.get("w"), "y":0, "w":w, "h":self.root.get("h") }60}61node = self.find_node(self.root, w, h)62if node:63return self.split_node(node, w, h)64else:65return None6667def grow_down(self, w, h):68self.root = {69"used": True,70"x":0,71"y":0,72"w":self.root.get("w"),73"h":self.root.get("h") + h,74"down": { "x": 0, "y": self.root.get("h"), "w": self.root.get("w"), "h": h },75"right": self.root76}77node = self.find_node(self.root, w, h)78if node:79return self.split_node(node, w, h)80else:81return None828384# This algorithm does not change the order of the sprites (so no "scrambling" of sprites)85# but at the cost of being *slightly* less space-efficient (so you get a slightly bigger spritesheet)86class OrderedPacker:87def __init__(self):88self.blocks = None89self.root = None # not actually a root but named so for consistency9091def fit(self, blocks):92self.blocks = blocks9394blocks_per_row = int(self._get_blocks_per_row_estimate())95blocks_matrix = []96for i in range(len(self.blocks)//blocks_per_row + 1):97blocks_matrix.append( self.blocks[i*blocks_per_row:(i+1)*blocks_per_row] )9899if blocks_matrix[-1] == []:100blocks_matrix = blocks_matrix[:-1]101102final_w = self._get_final_width(blocks_matrix)103final_h = self._get_final_height(blocks_matrix)104105self.root = {106'w': final_w,107'h': final_h108}109110curr_x = 0111curr_y = 0112max_heights = [ max([b["h"] for b in row]) for row in blocks_matrix ]113114for i, row in enumerate(blocks_matrix):115for bl in row:116bl["fit"] = {117'x': curr_x,118'y': curr_y119}120curr_x += bl["w"]121curr_y += max_heights[i]122curr_x = 0123124125def _get_blocks_per_row_estimate(self):126tot_area = sum([ x["w"]*x["h"] for x in self.blocks ])127estimated_sidelen = tot_area**0.5128avg_width = self._get_total_width()/len(self.blocks)129return estimated_sidelen // avg_width130131def _get_total_width(self):132return sum([ x["w"] for x in self.blocks ])133134def _get_final_width(self, rows):135# maximum value of total width among all the rows136row_sums = [ sum([b["w"] for b in row]) for row in rows ]137return max(row_sums)138139def _get_final_height(self, rows):140# sum of the maximum heights of each row141max_heights = [ max([b["h"] for b in row]) for row in rows ]142return sum(max_heights)143144