@mark1russell7/packing 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts.map +1 -1
- package/build/index.js +102 -26
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/maxrects.ts +67 -17
- package/src/skyline.ts +24 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/packing",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.11",
|
|
5
5
|
"description": "Rectangle packing: shelf, guillotine and skyline strategies, push cascades and measures of a layout.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"rectangle-packing",
|
package/src/maxrects.ts
CHANGED
|
@@ -90,9 +90,14 @@ function inside(a: Box, b: Box): boolean {
|
|
|
90
90
|
function cut(free: Box[], b: Box, minW: number, minH: number): { free: Box[]; hits: number } {
|
|
91
91
|
const kept: Box[] = [];
|
|
92
92
|
const parts: Box[] = [];
|
|
93
|
+
// The side of the box at each part: 0 left, 1 right, 2 top, 3 bottom
|
|
94
|
+
const sides: number[] = [];
|
|
93
95
|
let hits = 0;
|
|
94
|
-
const add = (x0: number, y0: number, x1: number, y1: number): void => {
|
|
95
|
-
if (x1 - x0 >= minW && y1 - y0 >= minH)
|
|
96
|
+
const add = (side: number, x0: number, y0: number, x1: number, y1: number): void => {
|
|
97
|
+
if (x1 - x0 >= minW && y1 - y0 >= minH) {
|
|
98
|
+
parts.push({ x0, y0, x1, y1 });
|
|
99
|
+
sides.push(side);
|
|
100
|
+
}
|
|
96
101
|
};
|
|
97
102
|
for (const f of free) {
|
|
98
103
|
if (b.x0 >= f.x1 || b.x1 <= f.x0 || b.y0 >= f.y1 || b.y1 <= f.y0) {
|
|
@@ -100,28 +105,67 @@ function cut(free: Box[], b: Box, minW: number, minH: number): { free: Box[]; hi
|
|
|
100
105
|
continue;
|
|
101
106
|
}
|
|
102
107
|
hits++;
|
|
103
|
-
if (b.x0 > f.x0) add(f.x0, f.y0, b.x0, f.y1);
|
|
104
|
-
if (b.x1 < f.x1) add(b.x1, f.y0, f.x1, f.y1);
|
|
105
|
-
if (b.y0 > f.y0) add(f.x0, f.y0, f.x1, b.y0);
|
|
106
|
-
if (b.y1 < f.y1) add(f.x0, b.y1, f.x1, f.y1);
|
|
108
|
+
if (b.x0 > f.x0) add(0, f.x0, f.y0, b.x0, f.y1);
|
|
109
|
+
if (b.x1 < f.x1) add(1, b.x1, f.y0, f.x1, f.y1);
|
|
110
|
+
if (b.y0 > f.y0) add(2, f.x0, f.y0, f.x1, b.y0);
|
|
111
|
+
if (b.y1 < f.y1) add(3, f.x0, b.y1, f.x1, f.y1);
|
|
107
112
|
}
|
|
113
|
+
if (parts.length === 0) return { free: kept, hits };
|
|
108
114
|
// Each part is in the free rectangle that it comes from. Thus a kept rectangle is never in a part.
|
|
109
|
-
|
|
115
|
+
// A part spans the box on one axis and touches the box on the other axis. Thus a part of one side is not in a part of
|
|
116
|
+
// another side. A kept rectangle with the part in it has an edge on the same side of the box.
|
|
117
|
+
const near: Box[][] = [[], [], [], []];
|
|
118
|
+
for (const k of kept) {
|
|
119
|
+
if (k.x1 === b.x0) near[0]!.push(k);
|
|
120
|
+
if (k.x0 === b.x1) near[1]!.push(k);
|
|
121
|
+
if (k.y1 === b.y0) near[2]!.push(k);
|
|
122
|
+
if (k.y0 === b.y1) near[3]!.push(k);
|
|
123
|
+
}
|
|
124
|
+
// In the order of `byExtent`, a part comes after each part that holds it. Of two equal parts, the first part stays.
|
|
125
|
+
// Thus a part goes when a part that stays before it holds it.
|
|
126
|
+
const order = parts.map((_, i) => i).sort((i, j) => byExtent(parts[i]!, parts[j]!) || i - j);
|
|
127
|
+
const gone = new Uint8Array(parts.length);
|
|
128
|
+
const stay: Box[][] = [[], [], [], []];
|
|
129
|
+
for (const i of order) {
|
|
130
|
+
const p = parts[i]!;
|
|
131
|
+
const own = stay[sides[i]!]!;
|
|
132
|
+
let drop = false;
|
|
133
|
+
for (const q of own) {
|
|
134
|
+
if (inside(p, q)) {
|
|
135
|
+
drop = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (drop) gone[i] = 1;
|
|
140
|
+
else own.push(p);
|
|
141
|
+
}
|
|
110
142
|
for (let i = 0; i < parts.length; i++) {
|
|
143
|
+
if (gone[i] === 1) continue;
|
|
111
144
|
const p = parts[i]!;
|
|
112
145
|
let drop = false;
|
|
113
|
-
for (
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
146
|
+
for (const k of near[sides[i]!]!) {
|
|
147
|
+
if (inside(p, k)) {
|
|
148
|
+
drop = true;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
118
151
|
}
|
|
119
|
-
for (let k = 0; k < count && !drop; k++) if (inside(p, kept[k]!)) drop = true;
|
|
120
152
|
if (!drop) kept.push(p);
|
|
121
153
|
}
|
|
122
154
|
return { free: kept, hits };
|
|
123
155
|
}
|
|
124
156
|
|
|
157
|
+
/**
|
|
158
|
+
* This function orders two boxes. The smaller `x0` comes first, then the smaller `y0`, then the larger `x1`, then the
|
|
159
|
+
* larger `y1`. Thus a box comes before each other box in it.
|
|
160
|
+
*/
|
|
161
|
+
function byExtent(a: Box, b: Box): number {
|
|
162
|
+
if (a.x0 !== b.x0) return a.x0 < b.x0 ? -1 : 1;
|
|
163
|
+
if (a.y0 !== b.y0) return a.y0 < b.y0 ? -1 : 1;
|
|
164
|
+
if (a.x1 !== b.x1) return a.x1 > b.x1 ? -1 : 1;
|
|
165
|
+
if (a.y1 !== b.y1) return a.y1 > b.y1 ? -1 : 1;
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
125
169
|
/** The edges of the boxes on one axis: for each coordinate, the flat list of the ranges on the other axis. */
|
|
126
170
|
type Edges = Map<number, number[]>;
|
|
127
171
|
|
|
@@ -315,10 +359,16 @@ export function runMaxRects<T>(rects: Rect<T>[], outer: Rect<T>, options: MaxRec
|
|
|
315
359
|
s2 = f.y0 + bh;
|
|
316
360
|
break;
|
|
317
361
|
}
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (
|
|
362
|
+
const x = f.x0;
|
|
363
|
+
const y = f.y0;
|
|
364
|
+
// A choice object comes only for a better place
|
|
365
|
+
if (best === undefined || s1 < best.s1 || (s1 === best.s1 && (s2 < best.s2 || (s2 === best.s2 && (y < best.y || (y === best.y && x < best.x)))))) {
|
|
366
|
+
best = { index: i, turn: t === 1, x, y, w: bw, h: bh, s1, s2 };
|
|
367
|
+
}
|
|
368
|
+
// Only the move count of a rule other than `bl` reads the place of `bl`
|
|
369
|
+
if (rule === "bl") continue;
|
|
370
|
+
const ly = y + bh;
|
|
371
|
+
if (low === undefined || ly < low.s1 || (ly === low.s1 && x < low.s2)) low = { index: i, turn: t === 1, x, y, w: bw, h: bh, s1: ly, s2: x };
|
|
322
372
|
}
|
|
323
373
|
}
|
|
324
374
|
if (best === undefined || !check || space.fits({ x: best.x + ox, y: best.y + oy, w: best.w, h: best.h })) break;
|
package/src/skyline.ts
CHANGED
|
@@ -156,6 +156,9 @@ export function packSkyline<T>(rects: Rect<T>[], outer: Rect<T>, options: Skylin
|
|
|
156
156
|
/**
|
|
157
157
|
* This function packs rectangles with a skyline in a free space. Each cell has a free range: the skyline is its start,
|
|
158
158
|
* and `limit` is its end. A cell with less room than the lowest rectangle left goes to its next free range.
|
|
159
|
+
*
|
|
160
|
+
* A rectangle that needs more cells than the outer rectangle has goes at the left edge. Its y is the first y where each
|
|
161
|
+
* cell is free over its height. Then each cell starts after the rectangle, as in the plain skyline.
|
|
159
162
|
*/
|
|
160
163
|
function packSkylineInSpace<T>(rects: Rect<T>[], outer: Rect<T>, order: SortOrder, space: FreeSpace): Rect<T> {
|
|
161
164
|
const width = Math.floor(outer.size.x);
|
|
@@ -198,9 +201,29 @@ function packSkylineInSpace<T>(rects: Rect<T>[], outer: Rect<T>, order: SortOrde
|
|
|
198
201
|
x = 0;
|
|
199
202
|
y = 0;
|
|
200
203
|
for (let i = 0; i < width; i++) if (Number.isFinite(skyline[i]!) && skyline[i]! > y) y = skyline[i]!;
|
|
204
|
+
// The rectangle covers each cell, thus it goes to the first y where each cell is free over its height
|
|
205
|
+
for (let moved = true; moved; ) {
|
|
206
|
+
moved = false;
|
|
207
|
+
for (let i = 0; i < width; i++) {
|
|
208
|
+
if (!Number.isFinite(skyline[i]!)) continue;
|
|
209
|
+
const [s, e] = rangeOf(i, y);
|
|
210
|
+
const next = s > y ? s : e < y + rh ? rangeOf(i, e)[0] : y;
|
|
211
|
+
if (next > y && Number.isFinite(next)) {
|
|
212
|
+
y = next;
|
|
213
|
+
moved = true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
201
217
|
rect.position.set(x + ox, y + oy);
|
|
202
218
|
rect.wasPacked = true;
|
|
203
|
-
|
|
219
|
+
const top = y + rh;
|
|
220
|
+
// Each cell starts after the rectangle. A cell with a range that ends before the top of the rectangle goes to its next range.
|
|
221
|
+
for (let i = 0; i < width; i++) {
|
|
222
|
+
if (!Number.isFinite(skyline[i]!)) continue;
|
|
223
|
+
if (top <= limit[i]!) skyline[i] = top;
|
|
224
|
+
else [skyline[i], limit[i]] = rangeOf(i, top);
|
|
225
|
+
}
|
|
226
|
+
outer.size.y = Math.max(outer.size.y, top);
|
|
204
227
|
continue;
|
|
205
228
|
}
|
|
206
229
|
let found = findWindow(skyline, width, rw, limit, rh);
|