1 /** 2 Copyright: Copyright (c) 2013-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 /// Rewrite of Jukka Jylanki's RectangleBinPacker 8 module voxelman.geometry.rectbinpacker; 9 10 import voxelman.math; 11 12 struct Node 13 { 14 irect rect; 15 Node* left, right; 16 } 17 18 struct RectBinPacker 19 { 20 this(in uint width, in uint height, in uint x = 0, in uint y = 0) 21 { 22 binWidth = width; 23 binHeight = height; 24 root.left = root.right = null; 25 root.rect.x = x; 26 root.rect.y = y; 27 root.rect.width = width; 28 root.rect.height = height; 29 } 30 31 /** 32 * Running time is linear to the number of rectangles already packed. Recursively calls itself. 33 * @Returns: null If the insertion didn't succeed. 34 */ 35 Node* insert(ivec2 size) 36 { 37 return insert(&root, size.x, size.y); 38 } 39 40 /++ 41 + @Returns: A value [0, 1] denoting the ratio of total surface area that is in use. 42 + 0.0f - the bin is totally empty, 1.0f - the bin is full. 43 +/ 44 float occupancy() 45 { 46 ulong totalSurfaceArea = binWidth * binHeight; 47 ulong area = usedSurfaceArea(&root); 48 49 return cast(float)area/totalSurfaceArea; 50 } 51 52 private: 53 54 uint binWidth; 55 uint binHeight; 56 57 Node root; 58 59 Node* insert(Node* node, in uint width, in uint height) 60 { 61 if (node.left !is null || node.right !is null) 62 { 63 if (node.left !is null) 64 { 65 Node* newNode = insert(node.left, width, height); 66 if (newNode) 67 return newNode; 68 } 69 if (node.right !is null) 70 { 71 Node* newNode = insert(node.right, width, height); 72 if (newNode !is null) 73 return newNode; 74 } 75 return null; // Didn't fit into either subtree! 76 } 77 78 if (width > node.rect.width || height > node.rect.height) 79 return null; // no space. 80 81 int w = node.rect.width - width; 82 int h = node.rect.height - height; 83 84 node.left = new Node(); 85 node.right = new Node(); 86 87 if (w <= h) // Split the remaining space in horizontal direction. 88 { 89 node.left.rect.x = node.rect.x + width; 90 node.left.rect.y = node.rect.y; 91 node.left.rect.width = w; 92 node.left.rect.height = height; 93 94 node.right.rect.x = node.rect.x; 95 node.right.rect.y = node.rect.y + height; 96 node.right.rect.width = node.rect.width; 97 node.right.rect.height = h; 98 } 99 else // Split the remaining space in vertical direction. 100 { 101 node.left.rect.x = node.rect.x; 102 node.left.rect.y = node.rect.y + height; 103 node.left.rect.width = width; 104 node.left.rect.height = h; 105 106 node.right.rect.x = node.rect.x + width; 107 node.right.rect.y = node.rect.y; 108 node.right.rect.width = w; 109 node.right.rect.height = node.rect.height; 110 } 111 112 node.rect.width = width; 113 node.rect.height = height; 114 return node; 115 } 116 117 ulong usedSurfaceArea(Node* node) const 118 { 119 if (node.left || node.right) 120 { 121 ulong result = node.rect.width * node.rect.height; 122 123 if (node.left !is null) 124 { 125 result += usedSurfaceArea(node.left); 126 } 127 if (node.right !is null) 128 { 129 result += usedSurfaceArea(node.right); 130 } 131 132 return result; 133 } 134 135 // This is a leaf node, it doesn't constitute to the total surface area. 136 return 0; 137 } 138 }