@mark1russell7/packing 0.2.10 → 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/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.10",
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) parts.push({ x0, y0, x1, y1 });
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
- const count = kept.length;
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 (let j = 0; j < parts.length && !drop; j++) {
114
- if (j === i) continue;
115
- const q = parts[j]!;
116
- // Of two equal parts, the first part stays
117
- if (inside(p, q) && (j < i || !inside(q, p))) drop = true;
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 c: Choice = { index: i, turn: t === 1, x: f.x0, y: f.y0, w: bw, h: bh, s1, s2 };
319
- if (best === undefined || s1 < best.s1 || (s1 === best.s1 && (s2 < best.s2 || (s2 === best.s2 && (c.y < best.y || (c.y === best.y && c.x < best.x)))))) best = c;
320
- const ly = f.y0 + bh;
321
- if (low === undefined || ly < low.s1 || (ly === low.s1 && f.x0 < low.s2)) low = { ...c, s1: ly, s2: f.x0 };
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;