Completed
Push — master ( 9610e0...320eee )
by Doug
08:43
created

OrientatedItemFactory.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 33
    public function getBestOrientation(Box $box, Item $item, OrientatedItem $prevItem = null, Item $nextItem = null, $widthLeft, $lengthLeft, $depthLeft) {
33
34 33
        $possibleOrientations = $this->getPossibleOrientations($item, $prevItem, $widthLeft, $lengthLeft, $depthLeft);
35 33
        $usableOrientations = $this->getUsableOrientations($possibleOrientations, $box, $item, $prevItem, $nextItem);
36
37 33
        $orientationFits = [];
38
        /** @var OrientatedItem $orientation */
39 33
        foreach ($usableOrientations as $o => $orientation) {
40 33
            $orientationFit = min($widthLeft - $orientation->getWidth(), $lengthLeft - $orientation->getLength());
41 33
            $orientationFits[$o] = $orientationFit;
42
        }
43
44 33
        if (!empty($orientationFits)) {
45 33
            asort($orientationFits);
46 33
            reset($orientationFits);
47 33
            $bestFit = $usableOrientations[key($orientationFits)];
48 33
            $this->logger->debug("Selected best fit orientation", ['orientation' => $bestFit]);
49 33
            return $bestFit;
50
        } else {
51 31
            return false;
52
        }
53
    }
54
55
    /**
56
     * Find all possible orientations for an item
57
     * @param Item $item
58
     * @param OrientatedItem|null $prevItem
59
     * @param int $widthLeft
60
     * @param int $lengthLeft
61
     * @param int $depthLeft
62
     * @return OrientatedItem[]
63
     */
64 33
    public function getPossibleOrientations(Item $item, OrientatedItem $prevItem = null, $widthLeft, $lengthLeft, $depthLeft) {
65
66 33
        $orientations = [];
67
68
        //Special case items that are the same as what we just packed - keep orientation
69 33
        if ($prevItem && $prevItem->getItem() == $item) {
70 13
            $orientations[] = new OrientatedItem($item, $prevItem->getWidth(), $prevItem->getLength(), $prevItem->getDepth());
71
        } else {
72
73
            //simple 2D rotation
74 33
            $orientations[] = new OrientatedItem($item, $item->getWidth(), $item->getLength(), $item->getDepth());
75 33
            $orientations[] = new OrientatedItem($item, $item->getLength(), $item->getWidth(), $item->getDepth());
76
77
            //add 3D rotation if we're allowed
78 33
            if (!$item->getKeepFlat()) {
79 12
                $orientations[] = new OrientatedItem($item, $item->getWidth(), $item->getDepth(), $item->getLength());
80 12
                $orientations[] = new OrientatedItem($item, $item->getLength(), $item->getDepth(), $item->getWidth());
81 12
                $orientations[] = new OrientatedItem($item, $item->getDepth(), $item->getWidth(), $item->getLength());
82 12
                $orientations[] = new OrientatedItem($item, $item->getDepth(), $item->getLength(), $item->getWidth());
83
            }
84
        }
85
86
        //remove any that simply don't fit
87 33
        return array_filter($orientations, function(OrientatedItem $i) use ($widthLeft, $lengthLeft, $depthLeft) {
88 33
            return $i->getWidth() <= $widthLeft && $i->getLength() <= $lengthLeft && $i->getDepth() <= $depthLeft;
89 33
        });
90
    }
91
92
    /**
93
     * @param OrientatedItem[] $possibleOrientations
94
     * @param Box              $box
95
     * @param Item             $item
96
     * @param OrientatedItem   $prevItem
0 ignored issues
show
Should the type for parameter $prevItem not be null|OrientatedItem?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
97
     * @param Item             $nextItem
0 ignored issues
show
Should the type for parameter $nextItem not be null|Item?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
98
     *
99
     * @return array
100
     */
101 33
    protected function getUsableOrientations(
102
        $possibleOrientations,
103
        Box $box,
104
        Item $item,
105
        OrientatedItem $prevItem = null,
106
        Item $nextItem = null
107
    ) {
108
        /*
109
         * Divide possible orientations into stable (low centre of gravity) and unstable (high centre of gravity)
110
         */
111 33
        $stableOrientations = [];
112 33
        $unstableOrientations = [];
113
114 33
        foreach ($possibleOrientations as $o => $orientation) {
115 33
            if ($orientation->isStable()) {
116 30
                $stableOrientations[] = $orientation;
117
            } else {
118 33
                $unstableOrientations[] = $orientation;
119
            }
120
        }
121
122 33
        $orientationsToUse = [];
123
124
        /*
125
         * We prefer to use stable orientations only, but allow unstable ones if either
126
         * the item is the last one left to pack OR
127
         * the item doesn't fit in the box any other way
128
         */
129 33
        if (count($stableOrientations) > 0) {
130 30
            $orientationsToUse = $stableOrientations;
131 32
        } else if (count($unstableOrientations) > 0) {
132 4
            $orientationsInEmptyBox = $this->getPossibleOrientations(
133
                $item,
134
                $prevItem,
135 4
                $box->getInnerWidth(),
136 4
                $box->getInnerLength(),
137 4
                $box->getInnerDepth()
138
            );
139
140 4
            $stableOrientationsInEmptyBox = array_filter(
141
                $orientationsInEmptyBox,
142 4
                function(OrientatedItem $orientation) {
143 4
                    return $orientation->isStable();
144 4
                }
145
            );
146
147
            if (is_null($nextItem) || count($stableOrientationsInEmptyBox) == 0) {
148
                $orientationsToUse = $unstableOrientations;
149
            }
150
        }
151
152
        return $orientationsToUse;
153
    }
154
}
155
156