1 module ppc.backend.packer;
2 import ppc.backend;
3 
4 /++
5     Node used in generating a texture.
6 +/
7 struct FNode {
8     this(PVector origin, PVector size) {
9         this.origin = origin;
10         this.size = size;
11         this.empty = true;
12         this.left = null;
13         this.right = null;
14     }
15 
16     /// Origin of texture
17     PVector origin;
18     
19     /// Size of texture
20     PVector size;
21 
22     /// Wether the node is taken
23     bool empty = true;
24 
25     /// Node branch left
26     FNode* left;
27 
28     /// Node branch right
29     FNode* right;
30 }
31 
32 /++
33     Packing algorithm implementation
34 +/
35 class TexturePacker {
36     /// Size of texture so far
37     PSize textureSize;
38 
39     /// The output buffer
40     ubyte[] buffer;
41 
42     /// The root node for the packing
43     FNode* root;
44 
45     this() {
46         root = new FNode(PVector(0, 0), PVector(int.max, int.max));
47         textureSize = PSize(1024, 1024);
48         buffer = new ubyte[](1024*1024);
49     }
50 
51     /++
52         Packing algorithm
53 
54         Based on the packing algorithm from straypixels.net
55         https://straypixels.net/texture-packing-for-fonts/
56     +/
57     FNode* pack(FNode* node, PVector size) {
58         if (!node.empty) {
59             return null;
60         } else if (node.left !is null && node.right !is null) {
61             FNode* rval = pack(node.left, size);
62             return rval !is null ? rval : pack(node.right, size);
63         } else {
64             PVector realSize = PVector(node.size.x, node.size.y);
65 
66             // Calculate actual size if on boundary
67             if (node.origin.x + node.size.x == int.max) {
68                 realSize.x = textureSize.width-node.origin.x;
69             }
70             if (node.origin.y + node.size.y == int.max) {
71                 realSize.y = textureSize.height-node.origin.y;
72             }
73 
74             
75             if (node.size.x == size.x && node.size.y == size.y) {
76                 // Size is perfect, pack here
77                 node.empty = false;
78                 return node;
79             }
80 
81             // Not big enough?
82             if (realSize.x < size.x || realSize.y < size.y) {
83                 return null;
84             }
85 
86             FNode* left;
87             FNode* right;
88 
89             PVector remain = PVector(realSize.x - size.x, realSize.y - size.y);
90             bool vsplit = remain.x < remain.y;
91             if (remain.x == 0 && remain.y == 0) {
92                 // Edgecase, hitting border of texture atlas perfectly, split at border instead
93                 if (node.size.x > node.size.y) vsplit = false;
94                 else vsplit = true;
95             }
96 
97             if (vsplit) {
98                 left = new FNode(node.origin, PVector(node.size.x, size.y));
99                 right = new FNode(  PVector(node.origin.x, node.origin.y + size.y), 
100                                     PVector(node.size.x, node.size.y - size.y));
101             } else {
102                 left = new FNode(node.origin, PVector(size.x, node.size.y));
103                 right = new FNode(  PVector(node.origin.x + size.x, node.origin.y), 
104                                     PVector(node.size.x - size.x, node.size.y));
105             }
106 
107             node.left = left;
108             node.right = right;
109             return pack(node.left, size);
110         }
111     }
112 
113     void resizeBuffer(PSize newSize) {
114         ubyte[] newBuffer = new ubyte[](newSize.width*newSize.height);
115         foreach(y; 0..textureSize.height) {
116             foreach(x; 0..textureSize.width) {
117                 newBuffer[y * newSize.width + x] = buffer[y * textureSize.width + x];
118             }
119         }
120 
121         textureSize = newSize;
122         buffer = newBuffer;
123     }
124 
125     /++
126         Pack a texture
127 
128         Returns the position that the texture can be found at.
129     +/
130     PVector packTexture(ubyte[] textureBuffer, PVector size) {
131         FNode* node = pack(root, size);
132         if (node == null) {
133             this.resizeBuffer(PSize(textureSize.width*2, textureSize.height*2));
134             node = pack(root, size);
135 
136             assert(node !is null, "Was unable to pack texture!");
137         }
138 
139         assert(size.x == node.size.x, "Sizes did not match! This is as bug in the texture packer.");
140         assert(size.y == node.size.y, "Sizes did not match! This is as bug in the texture packer.");
141 
142         foreach (ly; 0..size.y) {
143             foreach(lx; 0..size.x) {
144                 int y = cast(int)node.origin.y + cast(int)ly;
145                 int x = cast(int)node.origin.x + cast(int)lx;
146                 this.buffer[y * textureSize.width + x] = textureBuffer[ly * size.x + lx];
147             }
148         }
149 
150         return node.origin;
151     }
152 }