Completed
Push — factor_out_orientations ( 5fabe5...6c5e91 )
by Doug
05:06
created

OrientatedItemFactory::getBestOrientation()   C

Complexity

Conditions 9
Paths 48

Size

Total Lines 65
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 65
ccs 32
cts 32
cp 1
rs 6.4766
c 0
b 0
f 0
cc 9
eloc 37
nc 48
nop 7
crap 9

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
 * @package BoxPacker
5
 * @author Doug Wright
6
 */
7
namespace DVDoug\BoxPacker;
8
9
use Psr\Log\LoggerAwareInterface;
10
use Psr\Log\LoggerAwareTrait;
11
12
/**
13
 * Figure out orientations for an item and a given set of dimensions
14
 * @author Doug Wright
15
 * @package BoxPacker
16
 */
17
class OrientatedItemFactory implements LoggerAwareInterface
18
{
19
    use LoggerAwareTrait;
20
21
    /**
22
     * Get the best orientation for an item
23
     * @param Box $box
24
     * @param Item $item
25
     * @param OrientatedItem|null $prevItem
26
     * @param Item|null $nextItem
27
     * @param int $widthLeft
28
     * @param int $lengthLeft
29
     * @param int $depthLeft
30
     * @return OrientatedItem|false
31
     */
32 30
    public function getBestOrientation(Box $box, Item $item, OrientatedItem $prevItem = null, Item $nextItem = null, $widthLeft, $lengthLeft, $depthLeft) {
33
34 30
        $orientations = $this->getPossibleOrientations($item, $prevItem, $widthLeft, $lengthLeft, $depthLeft);
35
36
        /*
37
         * Divide possible orientations into stable (low centre of gravity) and unstable (high centre of gravity)
38
         */
39 30
        $stableOrientations = [];
40 30
        $unstableOrientations = [];
41
42 30
        foreach ($orientations as $o => $orientation) {
43 30
            if ($orientation->isStable()) {
44 27
                $stableOrientations[] = $orientation;
45
            } else {
46 30
                $unstableOrientations[] = $orientation;
47
            }
48
        }
49
50 30
        $orientationsToUse = [];
51
52
        /*
53
         * We prefer to use stable orientations only, but allow unstable ones if either
54
         * the item is the last one left to pack OR
55
         * the item doesn't fit in the box any other way
56
         */
57 30
        if (count($stableOrientations) > 0) {
58 27
            $orientationsToUse = $stableOrientations;
59 29
        } else if (count($unstableOrientations) > 0) {
60 4
            $orientationsInEmptyBox = $this->getPossibleOrientations(
61
                $item,
62
                $prevItem,
63 4
                $box->getInnerWidth(),
64 4
                $box->getInnerLength(),
65 4
                $box->getInnerDepth()
66
            );
67
68 4
            $stableOrientationsInEmptyBox = array_filter(
69
                $orientationsInEmptyBox,
70
                function(OrientatedItem $orientation) {
71 4
                    return $orientation->isStable();
72 4
                }
73
            );
74
75 4
            if (is_null($nextItem) || count($stableOrientationsInEmptyBox) == 0) {
76 4
                $orientationsToUse = $unstableOrientations;
77
            }
78
        }
79
80 30
        $orientationFits = [];
81
        /** @var OrientatedItem $orientation */
82 30
        foreach ($orientationsToUse as $o => $orientation) {
83 30
            $orientationFit = min($widthLeft - $orientation->getWidth(), $lengthLeft  - $orientation->getLength());
84 30
            $orientationFits[$o] = $orientationFit;
85
        }
86
87 30
        if (!empty($orientationFits)) {
88 30
            asort($orientationFits);
89 30
            reset($orientationFits);
90 30
            $bestFit = $orientationsToUse[key($orientationFits)];
91 30
            $this->logger->debug("Selected best fit orientation", ['orientation' => $bestFit]);
92 30
            return $bestFit;
93
        } else {
94 28
            return false;
95
        }
96
    }
97
98
    /**
99
     * Find all possible orientations for an item
100
     * @param Item $item
101
     * @param OrientatedItem|null $prevItem
102
     * @param int $widthLeft
103
     * @param int $lengthLeft
104
     * @param int $depthLeft
105
     * @return OrientatedItem[]
106
     */
107 30
    public function getPossibleOrientations(Item $item, OrientatedItem $prevItem = null, $widthLeft, $lengthLeft, $depthLeft) {
108
109 30
        $orientations = [];
110
111
        //Special case items that are the same as what we just packed - keep orientation
112 30
        if ($prevItem && $prevItem->getItem() == $item) {
113 12
            $orientations[] = new OrientatedItem($item, $prevItem->getWidth(), $prevItem->getLength(), $prevItem->getDepth());
114
        } else {
115
116
            //simple 2D rotation
117 30
            $orientations[] = new OrientatedItem($item, $item->getWidth(), $item->getLength(), $item->getDepth());
118 30
            $orientations[] = new OrientatedItem($item, $item->getLength(), $item->getWidth(), $item->getDepth());
119
120
            //add 3D rotation if we're allowed
121 30
            if (!$item->getKeepFlat()) {
122 11
                $orientations[] = new OrientatedItem($item, $item->getWidth(), $item->getDepth(), $item->getLength());
123 11
                $orientations[] = new OrientatedItem($item, $item->getLength(), $item->getDepth(), $item->getWidth());
124 11
                $orientations[] = new OrientatedItem($item, $item->getDepth(), $item->getWidth(), $item->getLength());
125 11
                $orientations[] = new OrientatedItem($item, $item->getDepth(), $item->getLength(), $item->getWidth());
126
            }
127
        }
128
129
        //remove any that simply don't fit
130 30
        return array_filter($orientations, function (OrientatedItem $i) use ($widthLeft, $lengthLeft, $depthLeft) {
131 30
            return $i->getWidth() <= $widthLeft && $i->getLength() <= $lengthLeft && $i->getDepth() <= $depthLeft;
132 30
        });
133
    }
134
}
135
136