Passed
Push — 1.x ( 457fa0...308477 )
by Doug
03:11
created

VolumePacker::tryAndStackItemsIntoSpace()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 21
c 0
b 0
f 0
nc 3
nop 10
dl 0
loc 34
rs 9.584

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * Box packing (3D bin packing, knapsack problem).
4
 *
5
 * @author Doug Wright
6
 */
7
namespace DVDoug\BoxPacker;
8
9
use Psr\Log\LoggerAwareInterface;
10
use Psr\Log\LoggerInterface;
11
use Psr\Log\NullLogger;
12
13
/**
14
 * Actual packer.
15
 *
16
 * @author Doug Wright
17
 */
18
class VolumePacker implements LoggerAwareInterface
19
{
20
    /**
21
     * The logger instance.
22
     *
23
     * @var LoggerInterface
24
     */
25
    protected $logger;
26
27
    /**
28
     * Box to pack items into.
29
     *
30
     * @var Box
31
     */
32
    protected $box;
33
34
    /**
35
     * List of items to be packed.
36
     *
37
     * @var ItemList
38
     */
39
    protected $items;
40
41
    /**
42
     * Whether the packer is in single-pass mode.
43
     *
44
     * @var bool
45
     */
46
    protected $singlePassMode = false;
47
48
    /**
49
     * @var LayerPacker
50
     */
51
    private $layerPacker;
52
53
    /**
54
     * @var bool
55
     */
56
    private $hasConstrainedItems;
57
58
    /**
59
     * Constructor.
60
     */
61 42
    public function __construct(Box $box, ItemList $items)
62
    {
63 42
        $this->box = $box;
64 42
        $this->items = clone $items;
65
66 42
        $this->logger = new NullLogger();
67
68 42
        $this->hasConstrainedItems = $items->hasConstrainedItems();
69
70 42
        $this->layerPacker = new LayerPacker($this->box);
71 42
        $this->layerPacker->setLogger($this->logger);
72 42
    }
73
74
    /**
75
     * Sets a logger.
76
     */
77 18
    public function setLogger(LoggerInterface $logger)
78
    {
79 18
        $this->logger = $logger;
80 18
        $this->layerPacker->setLogger($logger);
81 18
    }
82
83
    /**
84
     * @internal
85
     */
86 37
    public function setSinglePassMode($singlePassMode)
87
    {
88 37
        $this->singlePassMode = $singlePassMode;
89 37
        $this->layerPacker->setSinglePassMode($singlePassMode);
90 37
    }
91
92
    /**
93
     * Pack as many items as possible into specific given box.
94
     *
95
     * @return PackedBox packed box
96
     */
97 42
    public function pack()
98
    {
99 42
        $this->logger->debug("[EVALUATING BOX] {$this->box->getReference()}", ['box' => $this->box]);
100
101 42
        $rotationsToTest = [false];
102 42
        if (!$this->singlePassMode) {
103 42
            $rotationsToTest[] = true;
104
        }
105
106 42
        $boxPermutations = [];
107 42
        foreach ($rotationsToTest as $rotation) {
108 42
            if ($rotation) {
109 11
                $boxWidth = $this->box->getInnerLength();
110 11
                $boxLength = $this->box->getInnerWidth();
111
            } else {
112 42
                $boxWidth = $this->box->getInnerWidth();
113 42
                $boxLength = $this->box->getInnerLength();
114
            }
115
116 42
            $boxPermutation = $this->packRotation($boxWidth, $boxLength);
117 42
            if ($boxPermutation->getItems()->count() === $this->items->count()) {
118 40
                return $boxPermutation;
119
            }
120
121 28
            $boxPermutations[] = $boxPermutation;
122
        }
123
124
        usort($boxPermutations, static function (PackedBox $a, PackedBox $b) {
125 10
            if ($a->getVolumeUtilisation() < $b->getVolumeUtilisation()) {
126
                return 1;
127
            }
128 10
            return -1;
129 28
        });
130
131 28
        return reset($boxPermutations);
132
    }
133
134
    /**
135
     * Pack as many items as possible into specific given box.
136
     *
137
     * @return PackedBox packed box
138
     */
139 42
    private function packRotation($boxWidth, $boxLength)
140
    {
141 42
        $this->logger->debug("[EVALUATING ROTATION] {$this->box->getReference()}", ['width' => $boxWidth, 'length' => $boxLength]);
142
143
        /** @var PackedLayer[] $layers */
144 42
        $layers = [];
145 42
        $items = clone $this->items;
146
147 42
        while ($items->count() > 0) {
148 42
            $layerStartDepth = static::getCurrentPackedDepth($layers);
149 42
            $packedItemList = $this->getPackedItemList($layers);
150
151
            //do a preliminary layer pack to get the depth used
152 42
            $preliminaryItems = clone $items;
153 42
            $preliminaryLayer = $this->layerPacker->packLayer($preliminaryItems, clone $packedItemList, $layers, $layerStartDepth, $boxWidth, $boxLength, $this->box->getInnerDepth() - $layerStartDepth, 0);
154 42
            if (count($preliminaryLayer->getItems()) === 0) {
155 27
                break;
156
            }
157
158 42
            if ($preliminaryLayer->getDepth() === $preliminaryLayer->getItems()[0]->getDepth()) { // preliminary === final
159 23
                $layers[] = $preliminaryLayer;
160 23
                $items = $preliminaryItems;
161
            } else { //redo with now-known-depth so that we can stack to that height from the first item
162 21
                $layers[] = $this->layerPacker->packLayer($items, $packedItemList, $layers, $layerStartDepth, $boxWidth, $boxLength, $this->box->getInnerDepth() - $layerStartDepth, $preliminaryLayer->getDepth());
163
            }
164
        }
165
166 42
        $layers = $this->correctLayerRotation($layers, $boxWidth);
167 42
        $layers = $this->stabiliseLayers($layers);
168
169 42
        return PackedBox::fromPackedItemList($this->box, $this->getPackedItemList($layers));
170
    }
171
172
    /**
173
     * During packing, it is quite possible that layers have been created that aren't physically stable
174
     * i.e. they overhang the ones below.
175
     *
176
     * This function reorders them so that the ones with the greatest surface area are placed at the bottom
177
     *
178
     * @param  PackedLayer[] $oldLayers
179
     * @return PackedLayer[]
180
     */
181 42
    private function stabiliseLayers(array $oldLayers)
182
    {
183 42
        if ($this->singlePassMode || $this->hasConstrainedItems) { // constraints include position, so cannot change
184 32
            return $oldLayers;
185
        }
186
187 42
        $stabiliser = new LayerStabiliser();
188
189 42
        return $stabiliser->stabilise($oldLayers);
190
    }
191
192
    /**
193
     * Swap back width/length of the packed items to match orientation of the box if needed.
194
     *
195
     * @param PackedLayer[] $oldLayers
196
     */
197 42
    private function correctLayerRotation(array $oldLayers, $boxWidth)
198
    {
199 42
        if ($this->box->getInnerWidth() === $boxWidth) {
200 42
            return $oldLayers;
201
        }
202
203 4
        $newLayers = [];
204 4
        foreach ($oldLayers as $originalLayer) {
205 4
            $newLayer = new PackedLayer();
206 4
            foreach ($originalLayer->getItems() as $item) {
207 4
                $packedItem = new PackedItem($item->getItem(), $item->getY(), $item->getX(), $item->getZ(), $item->getLength(), $item->getWidth(), $item->getDepth());
208 4
                $newLayer->insert($packedItem);
209
            }
210 4
            $newLayers[] = $newLayer;
211
        }
212
213 4
        return $newLayers;
214
    }
215
216
    /**
217
     * Generate a single list of items packed.
218
     * @param PackedLayer[] $layers
219
     */
220 42
    private function getPackedItemList(array $layers)
221
    {
222 42
        $packedItemList = new PackedItemList();
223 42
        foreach ($layers as $layer) {
224 42
            foreach ($layer->getItems() as $packedItem) {
225 42
                $packedItemList->insert($packedItem);
226
            }
227
        }
228
229 42
        return $packedItemList;
230
    }
231
232
    /**
233
     * Return the current packed depth.
234
     *
235
     * @param PackedLayer[] $layers
236
     */
237 42
    private static function getCurrentPackedDepth(array $layers)
238
    {
239 42
        $depth = 0;
240 42
        foreach ($layers as $layer) {
241 34
            $depth += $layer->getDepth();
242
        }
243
244 42
        return $depth;
245
    }
246
}
247