PackedBox::getRemainingWeight()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
1
<?php
2
3
/**
4
 * Box packing (3D bin packing, knapsack problem).
5
 *
6
 * @author Doug Wright
7
 */
8
declare(strict_types=1);
9
10
namespace DVDoug\BoxPacker;
11
12
use JsonSerializable;
13
14
use function iterator_to_array;
15
use function json_encode;
16
use function max;
17
use function round;
18
use function is_iterable;
19
use function count;
20
use function array_pop;
21
use function assert;
22
use function array_map;
23
use function spl_object_id;
24
25
use const JSON_THROW_ON_ERROR;
26
use const JSON_NUMERIC_CHECK;
27
use const JSON_UNESCAPED_UNICODE;
28
use const JSON_UNESCAPED_SLASHES;
29
30
/**
31
 * A "box" with items.
32
 */
33
readonly class PackedBox implements JsonSerializable
34
{
35
    protected int $itemWeight;
36
37
    protected float $volumeUtilisation;
38
39
    /**
40
     * Get packed weight.
41
     *
42
     * @return int weight in grams
43 14
     */
44
    public function getWeight(): int
45 14
    {
46
        return $this->box->getEmptyWeight() + $this->getItemWeight();
47
    }
48
49
    /**
50
     * Get packed weight of the items only.
51
     *
52
     * @return int weight in grams
53 15
     */
54
    public function getItemWeight(): int
55 15
    {
56 15
        if (!isset($this->itemWeight)) {
57 15
            $itemWeight = 0;
58 15
            foreach ($this->items as $item) {
59
                $itemWeight += $item->item->getWeight();
60 15
            }
61
            $this->itemWeight = $itemWeight;
62
        }
63 15
64
        return $this->itemWeight;
65
    }
66
67
    /**
68
     * Get remaining width inside box for another item.
69 1
     */
70
    public function getRemainingWidth(): int
71 1
    {
72
        return $this->box->getInnerWidth() - $this->getUsedWidth();
73
    }
74
75
    /**
76
     * Get remaining length inside box for another item.
77 1
     */
78
    public function getRemainingLength(): int
79 1
    {
80
        return $this->box->getInnerLength() - $this->getUsedLength();
81
    }
82
83
    /**
84
     * Get remaining depth inside box for another item.
85 1
     */
86
    public function getRemainingDepth(): int
87 1
    {
88
        return $this->box->getInnerDepth() - $this->getUsedDepth();
89
    }
90
91
    /**
92
     * Used width inside box for packing items.
93 5
     */
94
    public function getUsedWidth(): int
95 5
    {
96
        $maxWidth = 0;
97 5
98 5
        foreach ($this->items as $item) {
99
            $maxWidth = max($maxWidth, $item->x + $item->width);
100
        }
101 5
102
        return $maxWidth;
103
    }
104
105
    /**
106
     * Used length inside box for packing items.
107 5
     */
108
    public function getUsedLength(): int
109 5
    {
110
        $maxLength = 0;
111 5
112 5
        foreach ($this->items as $item) {
113
            $maxLength = max($maxLength, $item->y + $item->length);
114
        }
115 5
116
        return $maxLength;
117
    }
118
119
    /**
120
     * Used depth inside box for packing items.
121 5
     */
122
    public function getUsedDepth(): int
123 5
    {
124
        $maxDepth = 0;
125 5
126 5
        foreach ($this->items as $item) {
127
            $maxDepth = max($maxDepth, $item->z + $item->depth);
128
        }
129 5
130
        return $maxDepth;
131
    }
132
133
    /**
134
     * Get remaining weight inside box for another item.
135 1
     */
136
    public function getRemainingWeight(): int
137 1
    {
138
        return $this->box->getMaxWeight() - $this->getWeight();
139
    }
140 34
141
    public function getInnerVolume(): int
142 34
    {
143
        return $this->box->getInnerWidth() * $this->box->getInnerLength() * $this->box->getInnerDepth();
144
    }
145
146
    /**
147
     * Get used volume of the packed box.
148 33
     */
149
    public function getUsedVolume(): int
150 33
    {
151
        return $this->items->getVolume();
152
    }
153
154
    /**
155
     * Get unused volume of the packed box.
156 1
     */
157
    public function getUnusedVolume(): int
158 1
    {
159
        return $this->getInnerVolume() - $this->getUsedVolume();
160
    }
161
162
    /**
163
     * Get volume utilisation of the packed box.
164 33
     */
165
    public function getVolumeUtilisation(): float
166 33
    {
167 33
        if (!isset($this->volumeUtilisation)) {
168
            $this->volumeUtilisation = round($this->getUsedVolume() / ($this->getInnerVolume() ?: 1) * 100, 1);
169
        }
170 33
171
        return $this->volumeUtilisation;
172
    }
173
174
    /**
175
     * Create a custom website visualiser URL for this packing.
176 1
     */
177
    public function generateVisualisationURL(): string
178 1
    {
179 1
        $dedupedItems = $splIdToIntMap = [];
180 1
        $splIdIndex = 0;
181 1
        foreach ($this->items->asItemArray() as $item) {
182 1
            if (!isset($splIdToIntMap[spl_object_id($item)])) {
183
                $splIdToIntMap[spl_object_id($item)] = $splIdIndex++;
184 1
            }
185
            $dedupedItems[$splIdToIntMap[spl_object_id($item)]] = $item;
186
        }
187 1
188 1
        foreach ($dedupedItems as $item) {
189
            $data['items'][$splIdToIntMap[spl_object_id($item)]] = [$item->getDescription(), $item->getWidth(), $item->getLength(), $item->getDepth()];
190
        }
191 1
192 1
        $data['boxes'][] = [
193 1
            $this->box->getReference(),
194 1
            $this->box->getInnerWidth(),
195 1
            $this->box->getInnerLength(),
196 1
            $this->box->getInnerDepth(),
197 1
            array_map(
198 1
                fn (PackedItem $item) => [$splIdToIntMap[spl_object_id($item->item)], $item->x, $item->y, $item->z, $item->width, $item->length, $item->depth],
199 1
                iterator_to_array($this->items)
200 1
            ),
201
        ];
202 1
203
        return 'https://boxpacker.io/en/master/visualiser.html?packing=' . json_encode($data, flags: JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $data seems to be defined by a foreach iteration on line 188. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
204
    }
205
206
    public function __construct(public Box $box, public PackedItemList $items)
207
    {
208
        assert($this->assertPackingCompliesWithRealWorld());
209
    }
210 3
211
    public function jsonSerialize(): array
212 3
    {
213
        $userValues = [];
214 3
215 2
        if ($this->box instanceof JsonSerializable) {
216 2
            $userSerialisation = $this->box->jsonSerialize();
0 ignored issues
show
Bug introduced by
The method jsonSerialize() does not exist on DVDoug\BoxPacker\Box. It seems like you code against a sub-type of DVDoug\BoxPacker\Box such as DVDoug\BoxPacker\WorkingVolume or DVDoug\BoxPacker\Test\TestBox. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

216
            /** @scrutinizer ignore-call */ 
217
            $userSerialisation = $this->box->jsonSerialize();
Loading history...
217 1
            if (is_iterable($userSerialisation)) {
218
                $userValues = $userSerialisation;
219 1
            } else {
220
                $userValues = ['extra' => $userSerialisation];
221
            }
222
        }
223 3
224 3
        return [
225 3
            'box' => [
226 3
                ...$userValues,
227 3
                'reference' => $this->box->getReference(),
228 3
                'innerWidth' => $this->box->getInnerWidth(),
229 3
                'innerLength' => $this->box->getInnerLength(),
230 3
                'innerDepth' => $this->box->getInnerDepth(),
231 3
            ],
232 3
            'items' => iterator_to_array($this->items),
233
        ];
234
    }
235
236
    /**
237
     * Validate that all items are placed solely within the confines of the box, and that no two items are placed
238
     * into the same physical space.
239
     */
240
    private function assertPackingCompliesWithRealWorld(): true
0 ignored issues
show
Bug introduced by
The type DVDoug\BoxPacker\true was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
241
    {
242
        /** @var PackedItem[] $itemsToCheck */
243
        $itemsToCheck = iterator_to_array($this->items);
244
        while (count($itemsToCheck) > 0) {
245
            $itemToCheck = array_pop($itemsToCheck);
246
247
            assert($itemToCheck->x >= 0);
248
            assert($itemToCheck->x + $itemToCheck->width <= $this->box->getInnerWidth());
249
            assert($itemToCheck->y >= 0);
250
            assert($itemToCheck->y + $itemToCheck->length <= $this->box->getInnerLength());
251
            assert($itemToCheck->z >= 0);
252
            assert($itemToCheck->z + $itemToCheck->depth <= $this->box->getInnerDepth());
253
254
            foreach ($itemsToCheck as $otherItem) {
255
                $hasXOverlap = $itemToCheck->x < ($otherItem->x + $otherItem->width) && $otherItem->x < ($itemToCheck->x + $itemToCheck->width);
256
                $hasYOverlap = $itemToCheck->y < ($otherItem->y + $otherItem->length) && $otherItem->y < ($itemToCheck->y + $itemToCheck->length);
257
                $hasZOverlap = $itemToCheck->z < ($otherItem->z + $otherItem->depth) && $otherItem->z < ($itemToCheck->z + $itemToCheck->depth);
258
259
                $hasOverlap = $hasXOverlap && $hasYOverlap && $hasZOverlap;
260
                assert(!$hasOverlap);
261
            }
262
        }
263
264
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the type-hinted return DVDoug\BoxPacker\true.
Loading history...
265
    }
266
}
267