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

OrientatedItemFactory::calculateAdditionalItemsPackedWithThisOrientation()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 62
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 39
c 0
b 0
f 0
nc 4
nop 6
dl 0
loc 62
rs 8.9848

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\LoggerAwareTrait;
11
use Psr\Log\NullLogger;
12
13
/**
14
 * Figure out orientations for an item and a given set of dimensions.
15
 *
16
 * @author Doug Wright
17
 * @internal
18
 */
19
class OrientatedItemFactory implements LoggerAwareInterface
20
{
21
    use LoggerAwareTrait;
22
23
    /** @var Box */
24
    protected $box;
25
26
    /**
27
     * Whether the packer is in single-pass mode.
28
     *
29
     * @var bool
30
     */
31
    protected $singlePassMode = false;
32
33
    /**
34
     * @var OrientatedItem[]
35
     */
36
    protected static $emptyBoxCache = [];
37
38 43
    public function __construct(Box $box)
39
    {
40 43
        $this->box = $box;
41 43
        $this->logger = new NullLogger();
42 43
    }
43
44 37
    public function setSinglePassMode($singlePassMode)
45
    {
46 37
        $this->singlePassMode = $singlePassMode;
47 37
    }
48
49
    /**
50
     * Get the best orientation for an item.
51
     *
52
     * @param Item                $item
53
     * @param OrientatedItem|null $prevItem
54
     * @param ItemList            $nextItems
55
     * @param int                 $widthLeft
56
     * @param int                 $lengthLeft
57
     * @param int                 $depthLeft
58
     * @param int                 $rowLength
59
     * @param int                 $x
60
     * @param int                 $y
61
     * @param int                 $z
62
     * @param PackedItemList      $prevPackedItemList
63
     *
64
     * @return OrientatedItem|null
65
     */
66 42
    public function getBestOrientation(
67
        Item $item,
68
        OrientatedItem $prevItem = null,
69
        ItemList $nextItems,
70
        $widthLeft,
71
        $lengthLeft,
72
        $depthLeft,
73
        $rowLength,
74
        $x,
75
        $y,
76
        $z,
77
        PackedItemList $prevPackedItemList
78
    ) {
79 42
        $this->logger->debug(
80 42
            "evaluating item {$item->getDescription()} for fit",
81
            [
82 42
                'item' => $item,
83
                'space' => [
84 42
                    'widthLeft' => $widthLeft,
85 42
                    'lengthLeft' => $lengthLeft,
86 42
                    'depthLeft' => $depthLeft,
87
                ],
88
            ]
89
        );
90
91 42
        $possibleOrientations = $this->getPossibleOrientations($item, $prevItem, $widthLeft, $lengthLeft, $depthLeft, $x, $y, $z, $prevPackedItemList);
92 42
        $usableOrientations = $this->getUsableOrientations($item, $possibleOrientations);
93
94 42
        if (empty($usableOrientations)) {
95 38
            return null;
96
        }
97
98 42
        $sorter = new OrientatedItemSorter($this, $this->singlePassMode, $widthLeft, $lengthLeft, $depthLeft, $nextItems, $rowLength, $x, $y, $z, $prevPackedItemList);
99 42
        $sorter->setLogger($this->logger);
100 42
        usort($usableOrientations, $sorter);
101
102 42
        $this->logger->debug('Selected best fit orientation', ['orientation' => $usableOrientations[0]]);
103
104 42
        return $usableOrientations[0];
105
    }
106
107
    /**
108
     * Find all possible orientations for an item.
109
     *
110
     * @param Item                $item
111
     * @param OrientatedItem|null $prevItem
112
     * @param int                 $widthLeft
113
     * @param int                 $lengthLeft
114
     * @param int                 $depthLeft
115
     * @param int                 $x
116
     * @param int                 $y
117
     * @param int                 $z
118
     * @param PackedItemList      $prevPackedItemList
119
     *
120
     * @return OrientatedItem[]
121
     */
122 43
    public function getPossibleOrientations(
123
        Item $item,
124
        OrientatedItem $prevItem = null,
125
        $widthLeft,
126
        $lengthLeft,
127
        $depthLeft,
128
        $x,
129
        $y,
130
        $z,
131
        PackedItemList $prevPackedItemList
132
    ) {
133 43
        $permutations = $this->generatePermutations($item, $prevItem);
134
135
        //remove any that simply don't fit
136 43
        $orientations = [];
137 43
        foreach ($permutations as $dimensions) {
138 43
            if ($dimensions[0] <= $widthLeft && $dimensions[1] <= $lengthLeft && $dimensions[2] <= $depthLeft) {
139 42
                $orientations[] = new OrientatedItem($item, $dimensions[0], $dimensions[1], $dimensions[2]);
140
            }
141
        }
142
143 43
        if ($item instanceof ConstrainedPlacementItem && !$this->box instanceof WorkingVolume) {
144
            $orientations = array_filter($orientations, function (OrientatedItem $i) use ($x, $y, $z, $prevPackedItemList) {
145 2
                return $i->getItem()->canBePacked($this->box, clone $prevPackedItemList, $x, $y, $z, $i->getWidth(), $i->getLength(), $i->getDepth());
146 2
            });
147
        }
148
149 43
        return $orientations;
150
    }
151
152
    /**
153
     * @param  Item             $item
154
     * @return OrientatedItem[]
155
     */
156 19
    public function getPossibleOrientationsInEmptyBox(Item $item)
157
    {
158 19
        $cacheKey = $item->getWidth() .
159 19
            '|' .
160 19
            $item->getLength() .
161 19
            '|' .
162 19
            $item->getDepth() .
163 19
            '|' .
164 19
            $this->box->getInnerWidth() .
165 19
            '|' .
166 19
            $this->box->getInnerLength() .
167 19
            '|' .
168 19
            $this->box->getInnerDepth();
169
170 19
        if (isset(static::$emptyBoxCache[$cacheKey])) {
171 14
            $orientations = static::$emptyBoxCache[$cacheKey];
172
        } else {
173 18
            $orientations = $this->getPossibleOrientations(
174 18
                $item,
175 18
                null,
176 18
                $this->box->getInnerWidth(),
177 18
                $this->box->getInnerLength(),
178 18
                $this->box->getInnerDepth(),
179 18
                0,
180 18
                0,
181 18
                0,
182 18
                new PackedItemList()
183
            );
184 18
            static::$emptyBoxCache[$cacheKey] = $orientations;
185
        }
186
187 19
        return $orientations;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $orientations also could return the type DVDoug\BoxPacker\OrientatedItem which is incompatible with the documented return type DVDoug\BoxPacker\OrientatedItem[].
Loading history...
188
    }
189
190
    /**
191
     * @param Item             $item
192
     * @param OrientatedItem[] $possibleOrientations
193
     * @param bool             $isLastItem
194
     *
195
     * @return OrientatedItem[]
196
     */
197 42
    protected function getUsableOrientations(
198
        Item $item,
199
        array $possibleOrientations
200
    ) {
201 42
        $orientationsToUse = $stableOrientations = $unstableOrientations = [];
202
203
        // Divide possible orientations into stable (low centre of gravity) and unstable (high centre of gravity)
204 42
        foreach ($possibleOrientations as $orientation) {
205 42
            if ($orientation->isStable() || $this->box->getInnerDepth() === $orientation->getDepth()) {
206 41
                $stableOrientations[] = $orientation;
207
            } else {
208 1
                $unstableOrientations[] = $orientation;
209
            }
210
        }
211
212
        /*
213
         * We prefer to use stable orientations only, but allow unstable ones if
214
         * the item doesn't fit in the box any other way
215
         */
216 42
        if (count($stableOrientations) > 0) {
217 41
            $orientationsToUse = $stableOrientations;
218 38
        } elseif (count($unstableOrientations) > 0) {
219 1
            $stableOrientationsInEmptyBox = $this->getStableOrientationsInEmptyBox($item);
220
221 1
            if (count($stableOrientationsInEmptyBox) === 0) {
222 1
                $orientationsToUse = $unstableOrientations;
223
            }
224
        }
225
226 42
        return $orientationsToUse;
227
    }
228
229
    /**
230
     * Return the orientations for this item if it were to be placed into the box with nothing else.
231
     *
232
     * @param  Item  $item
233
     * @return array
234
     */
235 1
    protected function getStableOrientationsInEmptyBox(Item $item)
236
    {
237 1
        $orientationsInEmptyBox = $this->getPossibleOrientationsInEmptyBox($item);
238
239 1
        return array_filter(
240 1
            $orientationsInEmptyBox,
241
            function (OrientatedItem $orientation) {
242 1
                return $orientation->isStable();
243 1
            }
244
        );
245
    }
246
247 43
    private function generatePermutations(Item $item, OrientatedItem $prevItem = null)
248
    {
249 43
        $permutations = [];
250
251
        //Special case items that are the same as what we just packed - keep orientation
252 43
        if ($prevItem && $prevItem->isSameDimensions($item)) {
253 35
            $permutations[] = [$prevItem->getWidth(), $prevItem->getLength(), $prevItem->getDepth()];
254
        } else {
255
            //simple 2D rotation
256 43
            $permutations[] = [$item->getWidth(), $item->getLength(), $item->getDepth()];
257 43
            $permutations[] = [$item->getLength(), $item->getWidth(), $item->getDepth()];
258
        }
259
260 43
        return $permutations;
261
    }
262
}
263