@mark1russell7/packing 0.1.7 → 0.2.0
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/README.md +1 -1
- package/build/index.d.ts +46 -219
- package/build/index.d.ts.map +1 -1
- package/build/index.js +166 -279
- package/build/index.js.map +1 -1
- package/package.json +5 -2
- package/src/adapters.ts +16 -13
- package/src/check.ts +34 -27
- package/src/contract.ts +30 -192
- package/src/convert.ts +11 -15
- package/src/corpus.ts +3 -3
- package/src/frame.ts +37 -50
- package/src/generators.ts +11 -12
- package/src/index.ts +7 -3
- package/src/layout-input.ts +34 -43
- package/src/measures.ts +12 -15
- package/src/strategy.ts +3 -3
package/build/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_TOLERANCE, EPS, RECORD_EPS, boundsOf, boundsOf as boundsOf$1, checkInput, marginSides, regionHitsRect, translate, turnBox, turnPoint, turnPort, turnRegion } from "@mark1russell7/layout-contract";
|
|
1
2
|
// src/vector.ts
|
|
2
3
|
/** A mutable two-dimensional vector. */
|
|
3
4
|
var Vector = class Vector {
|
|
@@ -599,24 +600,9 @@ function pushCascade(placed, rects, options = {}) {
|
|
|
599
600
|
return [...moved];
|
|
600
601
|
}
|
|
601
602
|
// src/contract.ts
|
|
602
|
-
/** The tolerance for the comparison of two numbers in an engine. */
|
|
603
|
-
const EPS = 1e-6;
|
|
604
|
-
/** The tolerance for the comparison of two numbers of a file. The files round to 3 decimals. */
|
|
605
|
-
const RECORD_EPS = .001;
|
|
606
|
-
/** The default tolerances of the hard rules. Each record writes its tolerances in its top-level field `tolerance`. */
|
|
607
|
-
const DEFAULT_TOLERANCE = Object.freeze({
|
|
608
|
-
overlap: .001,
|
|
609
|
-
through: .5
|
|
610
|
-
});
|
|
611
603
|
/** This function gives the margin of an item on each side. A number is the same margin on each side. */
|
|
612
604
|
function marginOf(item) {
|
|
613
|
-
const m = item.margin
|
|
614
|
-
if (typeof m === "number") return {
|
|
615
|
-
top: m,
|
|
616
|
-
right: m,
|
|
617
|
-
bottom: m,
|
|
618
|
-
left: m
|
|
619
|
-
};
|
|
605
|
+
const m = marginSides(item.margin);
|
|
620
606
|
return {
|
|
621
607
|
top: m.top,
|
|
622
608
|
right: m.right,
|
|
@@ -624,94 +610,14 @@ function marginOf(item) {
|
|
|
624
610
|
left: m.left
|
|
625
611
|
};
|
|
626
612
|
}
|
|
627
|
-
/** The side of a port after a turn of 90 degrees clockwise. */
|
|
628
|
-
const TURNED_SIDE = {
|
|
629
|
-
top: "right",
|
|
630
|
-
right: "bottom",
|
|
631
|
-
bottom: "left",
|
|
632
|
-
left: "top"
|
|
633
|
-
};
|
|
634
|
-
/** This function gives the point `px`, `py` of an item of height `h` after a turn of 90 degrees clockwise. */
|
|
635
|
-
function turnPoint(px, py, h) {
|
|
636
|
-
return [h - py, px];
|
|
637
|
-
}
|
|
638
|
-
/** This function turns a port of an item of height `h` by 90 degrees clockwise. The side turns from top to right. */
|
|
639
|
-
function turnPort(port, h) {
|
|
640
|
-
const turned = {
|
|
641
|
-
id: port.id,
|
|
642
|
-
side: TURNED_SIDE[port.side]
|
|
643
|
-
};
|
|
644
|
-
if (port.x !== void 0 && port.y !== void 0) {
|
|
645
|
-
const [x, y] = turnPoint(port.x, port.y, h);
|
|
646
|
-
turned.x = x;
|
|
647
|
-
turned.y = y;
|
|
648
|
-
} else if (port.x !== void 0) turned.y = port.x;
|
|
649
|
-
else if (port.y !== void 0) turned.x = h - port.y;
|
|
650
|
-
return turned;
|
|
651
|
-
}
|
|
652
|
-
/** This function turns a region relative to an item of height `h` by 90 degrees clockwise. */
|
|
653
|
-
function turnRegion(region, h) {
|
|
654
|
-
switch (region.kind) {
|
|
655
|
-
case "rect": return {
|
|
656
|
-
kind: "rect",
|
|
657
|
-
x: h - region.y - region.h,
|
|
658
|
-
y: region.x,
|
|
659
|
-
w: region.h,
|
|
660
|
-
h: region.w
|
|
661
|
-
};
|
|
662
|
-
case "circle": {
|
|
663
|
-
const [cx, cy] = turnPoint(region.cx, region.cy, h);
|
|
664
|
-
return {
|
|
665
|
-
kind: "circle",
|
|
666
|
-
cx,
|
|
667
|
-
cy,
|
|
668
|
-
r: region.r
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
case "ellipse": {
|
|
672
|
-
const [cx, cy] = turnPoint(region.cx, region.cy, h);
|
|
673
|
-
return {
|
|
674
|
-
kind: "ellipse",
|
|
675
|
-
cx,
|
|
676
|
-
cy,
|
|
677
|
-
rx: region.ry,
|
|
678
|
-
ry: region.rx
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
case "polygon": {
|
|
682
|
-
const turnRing = (ring) => ring.map(([x, y]) => turnPoint(x, y, h));
|
|
683
|
-
const turned = {
|
|
684
|
-
kind: "polygon",
|
|
685
|
-
points: turnRing(region.points)
|
|
686
|
-
};
|
|
687
|
-
if (region.holes) turned.holes = region.holes.map(turnRing);
|
|
688
|
-
return turned;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
613
|
/**
|
|
693
614
|
* This function gives an item after a turn of 90 degrees clockwise, with the y axis down (decision 1 of
|
|
694
615
|
* `docs/PACKER.md`). The width and the height change places. The top-left corner, the id and the other fields stay.
|
|
695
616
|
* The ports, the shape and the margin turn with the item. The function does not change its argument.
|
|
617
|
+
* It is `turnBox` of the contract.
|
|
696
618
|
*/
|
|
697
619
|
function turnItem(item) {
|
|
698
|
-
|
|
699
|
-
...item,
|
|
700
|
-
w: item.h,
|
|
701
|
-
h: item.w
|
|
702
|
-
};
|
|
703
|
-
if (item.ports) turned.ports = item.ports.map((p) => turnPort(p, item.h));
|
|
704
|
-
if (item.shape) turned.shape = turnRegion(item.shape, item.h);
|
|
705
|
-
if (item.margin !== void 0 && typeof item.margin !== "number") {
|
|
706
|
-
const m = item.margin;
|
|
707
|
-
turned.margin = {
|
|
708
|
-
top: m.left,
|
|
709
|
-
right: m.top,
|
|
710
|
-
bottom: m.right,
|
|
711
|
-
left: m.bottom
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
return turned;
|
|
620
|
+
return turnBox(item);
|
|
715
621
|
}
|
|
716
622
|
// src/sha256.ts
|
|
717
623
|
/** The round constants of SHA-256. */
|
|
@@ -888,8 +794,7 @@ function canonicalJson(value) {
|
|
|
888
794
|
}
|
|
889
795
|
/** This function gives the hash of an input file: the first 16 hex characters of the SHA-256 of its canonical form without `hash`. */
|
|
890
796
|
function hashInput(file) {
|
|
891
|
-
const
|
|
892
|
-
delete rest.hash;
|
|
797
|
+
const { hash: _hash, ...rest } = file;
|
|
893
798
|
return sha256Hex(canonicalJson(rest)).slice(0, 16);
|
|
894
799
|
}
|
|
895
800
|
/** This function gives the scale of a count: the nearest of 1, 10, 100, 1000 and 10000 on a log scale. The count 0 gives `1`. */
|
|
@@ -900,15 +805,17 @@ function countCell(count) {
|
|
|
900
805
|
if (count <= 3162) return "1k";
|
|
901
806
|
return "10k";
|
|
902
807
|
}
|
|
903
|
-
/**
|
|
808
|
+
/**
|
|
809
|
+
* This function gives the packing input of a file. The boxes become the items, and the edges go.
|
|
810
|
+
* A file without a container gets `{ fixed: "none" }`, so the packer picks the size. It is `toPackInput` of the contract.
|
|
811
|
+
*/
|
|
904
812
|
function toPackInput(file) {
|
|
905
|
-
|
|
906
|
-
container: file.container,
|
|
907
|
-
items: file.boxes
|
|
813
|
+
return {
|
|
814
|
+
container: file.container ?? { fixed: "none" },
|
|
815
|
+
items: file.boxes,
|
|
816
|
+
...file.obstacles === void 0 ? {} : { obstacles: file.obstacles },
|
|
817
|
+
...file.gap === void 0 ? {} : { gap: file.gap }
|
|
908
818
|
};
|
|
909
|
-
if (file.obstacles !== void 0) input.obstacles = file.obstacles;
|
|
910
|
-
if (file.gap !== void 0) input.gap = file.gap;
|
|
911
|
-
return input;
|
|
912
819
|
}
|
|
913
820
|
// src/measures.ts
|
|
914
821
|
const spec = (id, family, unit, direction, min, max, guard) => ({
|
|
@@ -1030,13 +937,13 @@ function boxesOf$1(input, output) {
|
|
|
1030
937
|
* `RECORD_EPS` are on one line, and the left box comes first. Otherwise the upper box comes first.
|
|
1031
938
|
*/
|
|
1032
939
|
function readsBefore(a, b) {
|
|
1033
|
-
if (Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y) >
|
|
940
|
+
if (Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y) > RECORD_EPS) return a.x + RECORD_EPS < b.x;
|
|
1034
941
|
return a.y < b.y;
|
|
1035
942
|
}
|
|
1036
943
|
/** This function sums one measure over the stages of a trace. A trace without the measure gives 0. */
|
|
1037
944
|
function traceSum(output, id) {
|
|
1038
945
|
let sum = 0;
|
|
1039
|
-
for (const stage of output.trace) sum += stage.measures[id] ?? 0;
|
|
946
|
+
for (const stage of output.trace ?? []) sum += stage.measures?.[id] ?? 0;
|
|
1040
947
|
return sum;
|
|
1041
948
|
}
|
|
1042
949
|
/**
|
|
@@ -1122,7 +1029,7 @@ function measureChange(baseInput, baseOutput, changeInput, changeOutput) {
|
|
|
1122
1029
|
if (q === void 0) continue;
|
|
1123
1030
|
const dx = q.x - p.x;
|
|
1124
1031
|
const dy = q.y - p.y;
|
|
1125
|
-
if (Math.abs(dx) >
|
|
1032
|
+
if (Math.abs(dx) > RECORD_EPS || Math.abs(dy) > RECORD_EPS || (p.turn ?? 0) !== (q.turn ?? 0)) moved += 1;
|
|
1126
1033
|
distances.push(Math.hypot(dx, dy) / unit);
|
|
1127
1034
|
}
|
|
1128
1035
|
if (distances.length === 0) return {
|
|
@@ -1405,41 +1312,6 @@ const clock = globalThis.performance;
|
|
|
1405
1312
|
function now() {
|
|
1406
1313
|
return clock ? clock.now() : Date.now();
|
|
1407
1314
|
}
|
|
1408
|
-
/** This function gives the bounding box of a region. */
|
|
1409
|
-
function boundsOf(region) {
|
|
1410
|
-
switch (region.kind) {
|
|
1411
|
-
case "rect": return {
|
|
1412
|
-
x: region.x,
|
|
1413
|
-
y: region.y,
|
|
1414
|
-
w: region.w,
|
|
1415
|
-
h: region.h
|
|
1416
|
-
};
|
|
1417
|
-
case "circle": return {
|
|
1418
|
-
x: region.cx - region.r,
|
|
1419
|
-
y: region.cy - region.r,
|
|
1420
|
-
w: 2 * region.r,
|
|
1421
|
-
h: 2 * region.r
|
|
1422
|
-
};
|
|
1423
|
-
case "ellipse": return {
|
|
1424
|
-
x: region.cx - region.rx,
|
|
1425
|
-
y: region.cy - region.ry,
|
|
1426
|
-
w: 2 * region.rx,
|
|
1427
|
-
h: 2 * region.ry
|
|
1428
|
-
};
|
|
1429
|
-
case "polygon": {
|
|
1430
|
-
const xs = region.points.map((p) => p[0]);
|
|
1431
|
-
const ys = region.points.map((p) => p[1]);
|
|
1432
|
-
const x = Math.min(...xs);
|
|
1433
|
-
const y = Math.min(...ys);
|
|
1434
|
-
return {
|
|
1435
|
-
x,
|
|
1436
|
-
y,
|
|
1437
|
-
w: Math.max(...xs) - x,
|
|
1438
|
-
h: Math.max(...ys) - y
|
|
1439
|
-
};
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
1315
|
/** This function gives the rectangle of a container. A rectangle shape gives the origin and the sizes of the fixed sides. */
|
|
1444
1316
|
function containerBounds(input) {
|
|
1445
1317
|
const { container } = input;
|
|
@@ -1487,7 +1359,7 @@ function buildFrame(input, movable = false) {
|
|
|
1487
1359
|
if (item.locked === true && item.x !== void 0 && item.y !== void 0) {
|
|
1488
1360
|
rect.locked = true;
|
|
1489
1361
|
rect.position.set(item.x - dx - ox, item.y - dy - oy);
|
|
1490
|
-
} else if (rect.size.x > w +
|
|
1362
|
+
} else if (rect.size.x > w + EPS || rect.size.y > h + EPS) {
|
|
1491
1363
|
tooLarge.push(item.id);
|
|
1492
1364
|
continue;
|
|
1493
1365
|
}
|
|
@@ -1616,59 +1488,55 @@ function transposeRegion(region) {
|
|
|
1616
1488
|
};
|
|
1617
1489
|
case "polygon": {
|
|
1618
1490
|
const swap = (ring) => ring.map(([x, y]) => [y, x]);
|
|
1619
|
-
|
|
1491
|
+
return region.holes === void 0 ? {
|
|
1620
1492
|
kind: "polygon",
|
|
1621
1493
|
points: swap(region.points)
|
|
1494
|
+
} : {
|
|
1495
|
+
kind: "polygon",
|
|
1496
|
+
points: swap(region.points),
|
|
1497
|
+
holes: region.holes.map(swap)
|
|
1622
1498
|
};
|
|
1623
|
-
if (region.holes) turned.holes = region.holes.map(swap);
|
|
1624
|
-
return turned;
|
|
1625
1499
|
}
|
|
1626
1500
|
}
|
|
1627
1501
|
}
|
|
1628
1502
|
/** This function gives an item with the axes changed. The ports go, because the adapters of 0.1 do not read them. */
|
|
1629
1503
|
function transposeItem(item) {
|
|
1630
|
-
const { ports: _ports, ...rest } = item;
|
|
1631
|
-
|
|
1504
|
+
const { ports: _ports, x, y, shape, margin, ...rest } = item;
|
|
1505
|
+
return {
|
|
1632
1506
|
...rest,
|
|
1633
1507
|
w: item.h,
|
|
1634
|
-
h: item.w
|
|
1508
|
+
h: item.w,
|
|
1509
|
+
...y === void 0 ? {} : { x: y },
|
|
1510
|
+
...x === void 0 ? {} : { y: x },
|
|
1511
|
+
...shape === void 0 ? {} : { shape: transposeRegion(shape) },
|
|
1512
|
+
...margin === void 0 ? {} : { margin: typeof margin === "number" ? margin : {
|
|
1513
|
+
top: margin.left,
|
|
1514
|
+
right: margin.bottom,
|
|
1515
|
+
bottom: margin.right,
|
|
1516
|
+
left: margin.top
|
|
1517
|
+
} }
|
|
1635
1518
|
};
|
|
1636
|
-
delete turned.x;
|
|
1637
|
-
delete turned.y;
|
|
1638
|
-
if (item.y !== void 0) turned.x = item.y;
|
|
1639
|
-
if (item.x !== void 0) turned.y = item.x;
|
|
1640
|
-
if (item.shape) turned.shape = transposeRegion(item.shape);
|
|
1641
|
-
if (item.margin !== void 0 && typeof item.margin !== "number") {
|
|
1642
|
-
const m = item.margin;
|
|
1643
|
-
turned.margin = {
|
|
1644
|
-
top: m.left,
|
|
1645
|
-
right: m.bottom,
|
|
1646
|
-
bottom: m.right,
|
|
1647
|
-
left: m.top
|
|
1648
|
-
};
|
|
1649
|
-
}
|
|
1650
|
-
return turned;
|
|
1651
1519
|
}
|
|
1652
1520
|
/** This function gives an input with the axes changed. A fixed height becomes a fixed width. */
|
|
1653
1521
|
function transposeInput(input) {
|
|
1654
1522
|
const c = input.container;
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1523
|
+
return {
|
|
1524
|
+
container: {
|
|
1525
|
+
fixed: c.fixed === "height" ? "width" : c.fixed === "width" ? "height" : c.fixed,
|
|
1526
|
+
...c.h === void 0 ? {} : { w: c.h },
|
|
1527
|
+
...c.w === void 0 ? {} : { h: c.w },
|
|
1528
|
+
...c.aspect === void 0 ? {} : { aspect: 1 / c.aspect },
|
|
1529
|
+
...c.aspectWeight === void 0 ? {} : { aspectWeight: c.aspectWeight },
|
|
1530
|
+
...c.shape === void 0 ? {} : { shape: transposeRegion(c.shape) },
|
|
1531
|
+
...c.inset === void 0 ? {} : { inset: c.inset }
|
|
1532
|
+
},
|
|
1533
|
+
items: input.items.map(transposeItem),
|
|
1534
|
+
...input.obstacles === void 0 ? {} : { obstacles: input.obstacles.map((o) => ({
|
|
1535
|
+
...o,
|
|
1536
|
+
region: transposeRegion(o.region)
|
|
1537
|
+
})) },
|
|
1538
|
+
...input.gap === void 0 ? {} : { gap: input.gap }
|
|
1665
1539
|
};
|
|
1666
|
-
if (input.obstacles) turned.obstacles = input.obstacles.map((o) => ({
|
|
1667
|
-
...o,
|
|
1668
|
-
region: transposeRegion(o.region)
|
|
1669
|
-
}));
|
|
1670
|
-
if (input.gap !== void 0) turned.gap = input.gap;
|
|
1671
|
-
return turned;
|
|
1672
1540
|
}
|
|
1673
1541
|
/** This function gives an output with the axes changed. */
|
|
1674
1542
|
function transposeOutput(output) {
|
|
@@ -1688,7 +1556,7 @@ function transposeOutput(output) {
|
|
|
1688
1556
|
x: m.y,
|
|
1689
1557
|
y: m.x
|
|
1690
1558
|
})),
|
|
1691
|
-
trace: output.trace
|
|
1559
|
+
...output.trace === void 0 ? {} : { trace: output.trace }
|
|
1692
1560
|
};
|
|
1693
1561
|
}
|
|
1694
1562
|
/**
|
|
@@ -1698,13 +1566,14 @@ function transposeOutput(output) {
|
|
|
1698
1566
|
function viaAxes(input, previous, run) {
|
|
1699
1567
|
const start = now();
|
|
1700
1568
|
const inner = run(transposeInput(input), previous === void 0 ? void 0 : transposeOutput(previous));
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1569
|
+
return {
|
|
1570
|
+
...transposeOutput(inner),
|
|
1571
|
+
trace: [{
|
|
1572
|
+
id: "axes",
|
|
1573
|
+
ms: now() - start,
|
|
1574
|
+
measures: {}
|
|
1575
|
+
}, ...inner.trace ?? []]
|
|
1576
|
+
};
|
|
1708
1577
|
}
|
|
1709
1578
|
// src/adapters.ts
|
|
1710
1579
|
/** The cells of `rows`, `skyline` and `shelf`, without the movement of the obstacles. */
|
|
@@ -1745,12 +1614,14 @@ function packFixedWidth(id, input, options) {
|
|
|
1745
1614
|
sortOrder: options.sortOrder
|
|
1746
1615
|
});
|
|
1747
1616
|
const output = readFrame(input, frame, []);
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1617
|
+
return {
|
|
1618
|
+
...output,
|
|
1619
|
+
trace: [{
|
|
1620
|
+
id,
|
|
1621
|
+
ms: now() - start,
|
|
1622
|
+
measures: { "pack.overflow": output.overflow.length }
|
|
1623
|
+
}]
|
|
1624
|
+
};
|
|
1754
1625
|
}
|
|
1755
1626
|
/** This function makes the strategy of one function of `pack`. */
|
|
1756
1627
|
function layoutStrategy(id, family, catalog, cells) {
|
|
@@ -1840,24 +1711,26 @@ const logSweep = {
|
|
|
1840
1711
|
const ms = now() - start;
|
|
1841
1712
|
const runs = result.tried.length + 1;
|
|
1842
1713
|
const output = readFrame(input, frame, []);
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1714
|
+
return {
|
|
1715
|
+
...output,
|
|
1716
|
+
trace: [{
|
|
1717
|
+
id: "log-sweep",
|
|
1718
|
+
ms,
|
|
1719
|
+
measures: {
|
|
1720
|
+
"pack.search.widths": result.tried.length,
|
|
1721
|
+
"pack.search.ms": ms
|
|
1722
|
+
}
|
|
1723
|
+
}, {
|
|
1724
|
+
id,
|
|
1725
|
+
ms: ms / runs,
|
|
1726
|
+
measures: { "pack.overflow": output.overflow.length }
|
|
1727
|
+
}]
|
|
1728
|
+
};
|
|
1856
1729
|
}
|
|
1857
1730
|
};
|
|
1858
1731
|
/** This function tells if the insides of two rectangles overlap by more than `EPS`. */
|
|
1859
1732
|
function overlaps(a, b) {
|
|
1860
|
-
return Math.min(a.right(), b.right()) - Math.max(a.position.x, b.position.x) >
|
|
1733
|
+
return Math.min(a.right(), b.right()) - Math.max(a.position.x, b.position.x) > EPS && Math.min(a.bottom(), b.bottom()) - Math.max(a.position.y, b.position.y) > EPS;
|
|
1861
1734
|
}
|
|
1862
1735
|
/** This function packs an input with a fixed size or a fixed width with a push cascade. */
|
|
1863
1736
|
function pushFixedWidth(input, options, previous) {
|
|
@@ -1899,9 +1772,9 @@ function pushFixedWidth(input, options, previous) {
|
|
|
1899
1772
|
const startAt = starts.get(e);
|
|
1900
1773
|
const x = e.rect.position.x + e.dx + frame.ox;
|
|
1901
1774
|
const y = e.rect.position.y + e.dy + frame.oy;
|
|
1902
|
-
const b = boundsOf(e.obstacle.region);
|
|
1903
|
-
if (x < bounds.x -
|
|
1904
|
-
else if (Math.abs(e.rect.position.x - startAt.x) >
|
|
1775
|
+
const b = boundsOf$1(e.obstacle.region);
|
|
1776
|
+
if (x < bounds.x - EPS || y < bounds.y - EPS || x + b.w > bounds.x + bounds.w + EPS || y + b.h > bounds.y + bounds.h + EPS || lockedItems.some((l) => overlaps(l.rect, e.rect))) e.rect.position.set(startAt.x, startAt.y);
|
|
1777
|
+
else if (Math.abs(e.rect.position.x - startAt.x) > EPS || Math.abs(e.rect.position.y - startAt.y) > EPS) moved.push({
|
|
1905
1778
|
id: e.obstacle.id,
|
|
1906
1779
|
x,
|
|
1907
1780
|
y
|
|
@@ -1911,7 +1784,7 @@ function pushFixedWidth(input, options, previous) {
|
|
|
1911
1784
|
const kept = [...obstacles, ...lockedItems].map((e) => e.rect);
|
|
1912
1785
|
for (const e of positioned) {
|
|
1913
1786
|
const r = e.rect;
|
|
1914
|
-
if (r.position.x >= -
|
|
1787
|
+
if (r.position.x >= -EPS && r.position.y >= -EPS && r.right() <= frame.w + EPS && r.bottom() <= frame.h + EPS && !kept.some((k) => overlaps(k, r))) kept.push(r);
|
|
1915
1788
|
else pending.push(e);
|
|
1916
1789
|
}
|
|
1917
1790
|
const skylineStart = now();
|
|
@@ -1927,19 +1800,21 @@ function pushFixedWidth(input, options, previous) {
|
|
|
1927
1800
|
packSkyline([...pending.map((e) => e.rect), ...kept], outerOf(frame), { sortOrder: options.sortOrder });
|
|
1928
1801
|
}
|
|
1929
1802
|
const output = readFrame(input, frame, [], moved);
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1803
|
+
return {
|
|
1804
|
+
...output,
|
|
1805
|
+
trace: [{
|
|
1806
|
+
id: "push",
|
|
1807
|
+
ms: pushMs,
|
|
1808
|
+
measures: { "pack.push.moved": moved.length }
|
|
1809
|
+
}, {
|
|
1810
|
+
id: "skyline",
|
|
1811
|
+
ms: now() - skylineStart,
|
|
1812
|
+
measures: {
|
|
1813
|
+
"pack.push.replaced": pending.length,
|
|
1814
|
+
"pack.overflow": output.overflow.length
|
|
1815
|
+
}
|
|
1816
|
+
}]
|
|
1817
|
+
};
|
|
1943
1818
|
}
|
|
1944
1819
|
/**
|
|
1945
1820
|
* The strategy `push` uses `pushCascade`. Each item starts at its `x` and `y`, or at its place in `context.previous`.
|
|
@@ -1989,31 +1864,29 @@ const push = {
|
|
|
1989
1864
|
*/
|
|
1990
1865
|
function fromRects(rects, outer) {
|
|
1991
1866
|
const fixed = outer.fixedSize ? "size" : outer.fixedWidth ? "width" : "none";
|
|
1992
|
-
const container = { fixed };
|
|
1993
|
-
if (fixed !== "none") container.w = outer.size.x;
|
|
1994
|
-
if (fixed === "size") container.h = outer.size.y;
|
|
1995
|
-
if (outer.position.x !== 0 || outer.position.y !== 0) container.shape = {
|
|
1996
|
-
kind: "rect",
|
|
1997
|
-
x: outer.position.x,
|
|
1998
|
-
y: outer.position.y,
|
|
1999
|
-
w: outer.size.x,
|
|
2000
|
-
h: outer.size.y
|
|
2001
|
-
};
|
|
2002
1867
|
return {
|
|
2003
|
-
container
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
1868
|
+
container: {
|
|
1869
|
+
fixed,
|
|
1870
|
+
...fixed === "none" ? {} : { w: outer.size.x },
|
|
1871
|
+
...fixed === "size" ? { h: outer.size.y } : {},
|
|
1872
|
+
...outer.position.x !== 0 || outer.position.y !== 0 ? { shape: {
|
|
1873
|
+
kind: "rect",
|
|
1874
|
+
x: outer.position.x,
|
|
1875
|
+
y: outer.position.y,
|
|
1876
|
+
w: outer.size.x,
|
|
1877
|
+
h: outer.size.y
|
|
1878
|
+
} } : {}
|
|
1879
|
+
},
|
|
1880
|
+
items: rects.map((r, i) => ({
|
|
1881
|
+
id: String(i),
|
|
1882
|
+
w: r.size.x,
|
|
1883
|
+
h: r.size.y,
|
|
1884
|
+
...r.locked ? {
|
|
1885
|
+
locked: true,
|
|
1886
|
+
x: r.position.x,
|
|
1887
|
+
y: r.position.y
|
|
1888
|
+
} : {}
|
|
1889
|
+
}))
|
|
2017
1890
|
};
|
|
2018
1891
|
}
|
|
2019
1892
|
/**
|
|
@@ -2068,6 +1941,15 @@ function packWith(id, input, options, seed = 0) {
|
|
|
2068
1941
|
function overlap(a, b, tolerance) {
|
|
2069
1942
|
return Math.min(a.r, b.r) - Math.max(a.l, b.l) > tolerance && Math.min(a.b, b.b) - Math.max(a.t, b.t) > tolerance;
|
|
2070
1943
|
}
|
|
1944
|
+
/** This function gives the box made smaller by a tolerance on each side, as a rectangle. */
|
|
1945
|
+
function inner(box, tolerance) {
|
|
1946
|
+
return {
|
|
1947
|
+
x: box.l + tolerance,
|
|
1948
|
+
y: box.t + tolerance,
|
|
1949
|
+
w: box.r - box.l - 2 * tolerance,
|
|
1950
|
+
h: box.b - box.t - 2 * tolerance
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
2071
1953
|
/**
|
|
2072
1954
|
* This function counts the breaks of the 4 hard rules of the packer for an output.
|
|
2073
1955
|
*
|
|
@@ -2078,38 +1960,36 @@ function overlap(a, b, tolerance) {
|
|
|
2078
1960
|
* - `complete`: each item with no placement that is not in the overflow. A locked item breaks `locked-kept` in its place.
|
|
2079
1961
|
*
|
|
2080
1962
|
* A locked item with no placement stays at its input position for `no-overlap` and `in-container`. A locked item with no
|
|
2081
|
-
* `x` or no `y` in the input is
|
|
1963
|
+
* `x` or no `y` in the input is a bad input. The rules skip it, as `packHard` of the contract does. `checkInput` finds it.
|
|
2082
1964
|
*
|
|
2083
|
-
* The
|
|
2084
|
-
*
|
|
1965
|
+
* The test of an obstacle is exact: a circle, an ellipse and a polygon with holes count only their insides, as in `packHard`.
|
|
1966
|
+
* The rules read a shape of the container or of an item as its bounding box (decision 5). The bounding box of an item
|
|
1967
|
+
* shape is relative to the position of the item. The result has no tolerance in it.
|
|
2085
1968
|
*/
|
|
2086
1969
|
function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
2087
1970
|
const tol = tolerance.overlap;
|
|
2088
1971
|
const placements = /* @__PURE__ */ new Map();
|
|
2089
1972
|
for (const p of output.placements) if (!placements.has(p.id)) placements.set(p.id, p);
|
|
2090
1973
|
const overflow = new Set(output.overflow);
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
"in-container": 0,
|
|
2094
|
-
"locked-kept": 0,
|
|
2095
|
-
complete: 0
|
|
2096
|
-
};
|
|
1974
|
+
let lockedKept = 0;
|
|
1975
|
+
let complete = 0;
|
|
2097
1976
|
const boxes = [];
|
|
2098
1977
|
for (const item of input.items) {
|
|
2099
|
-
|
|
1978
|
+
if (item.locked === true && (item.x === void 0 || item.y === void 0)) continue;
|
|
1979
|
+
const lockedAt = item.locked === true ? {
|
|
2100
1980
|
x: item.x,
|
|
2101
1981
|
y: item.y
|
|
2102
1982
|
} : void 0;
|
|
2103
1983
|
let p = placements.get(item.id);
|
|
2104
1984
|
if (p === void 0) {
|
|
2105
1985
|
if (lockedAt === void 0) {
|
|
2106
|
-
if (!overflow.has(item.id))
|
|
1986
|
+
if (!overflow.has(item.id)) complete += 1;
|
|
2107
1987
|
continue;
|
|
2108
1988
|
}
|
|
2109
|
-
|
|
1989
|
+
lockedKept += 1;
|
|
2110
1990
|
p = lockedAt;
|
|
2111
1991
|
} else if (lockedAt !== void 0) {
|
|
2112
|
-
if (Math.abs(p.x - lockedAt.x) >
|
|
1992
|
+
if (Math.abs(p.x - lockedAt.x) > RECORD_EPS || Math.abs(p.y - lockedAt.y) > RECORD_EPS) lockedKept += 1;
|
|
2113
1993
|
}
|
|
2114
1994
|
const placed = p.turn === 90 ? turnItem(item) : item;
|
|
2115
1995
|
const m = marginOf(placed);
|
|
@@ -2130,18 +2010,17 @@ function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
|
2130
2010
|
}
|
|
2131
2011
|
const moved = new Map(output.moved.map((m) => [m.id, m]));
|
|
2132
2012
|
const obstacles = (input.obstacles ?? []).map((o) => {
|
|
2013
|
+
const at = moved.get(o.id);
|
|
2014
|
+
if (at === void 0) return o.region;
|
|
2133
2015
|
const b = boundsOf(o.region);
|
|
2134
|
-
|
|
2135
|
-
return {
|
|
2136
|
-
l: at.x,
|
|
2137
|
-
t: at.y,
|
|
2138
|
-
r: at.x + b.w,
|
|
2139
|
-
b: at.y + b.h
|
|
2140
|
-
};
|
|
2016
|
+
return translate(o.region, at.x - b.x, at.y - b.y);
|
|
2141
2017
|
});
|
|
2018
|
+
let overlaps = 0;
|
|
2142
2019
|
for (const [i, a] of boxes.entries()) {
|
|
2143
|
-
for (let j = i + 1; j < boxes.length; j++) if (overlap(a, boxes[j], tol))
|
|
2144
|
-
|
|
2020
|
+
for (let j = i + 1; j < boxes.length; j++) if (overlap(a, boxes[j], tol)) overlaps += 1;
|
|
2021
|
+
if (obstacles.length === 0) continue;
|
|
2022
|
+
const rect = inner(a, tol);
|
|
2023
|
+
for (const region of obstacles) if (regionHitsRect(region, rect)) overlaps += 1;
|
|
2145
2024
|
}
|
|
2146
2025
|
const { container } = input;
|
|
2147
2026
|
const shape = container.shape === void 0 ? void 0 : boundsOf(container.shape);
|
|
@@ -2152,12 +2031,18 @@ function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
|
2152
2031
|
const inset = container.inset ?? 0;
|
|
2153
2032
|
const fixedW = container.fixed === "size" || container.fixed === "width";
|
|
2154
2033
|
const fixedH = container.fixed === "size" || container.fixed === "height";
|
|
2034
|
+
let outside = 0;
|
|
2155
2035
|
for (const box of boxes) {
|
|
2156
2036
|
const outX = fixedW && (box.l < x + inset - tol || box.r > x + w - inset + tol);
|
|
2157
2037
|
const outY = fixedH && (box.t < y + inset - tol || box.b > y + h - inset + tol);
|
|
2158
|
-
if (outX || outY)
|
|
2038
|
+
if (outX || outY) outside += 1;
|
|
2159
2039
|
}
|
|
2160
|
-
return
|
|
2040
|
+
return {
|
|
2041
|
+
"no-overlap": overlaps,
|
|
2042
|
+
"in-container": outside,
|
|
2043
|
+
"locked-kept": lockedKept,
|
|
2044
|
+
complete
|
|
2045
|
+
};
|
|
2161
2046
|
}
|
|
2162
2047
|
// src/generators.ts
|
|
2163
2048
|
/** The largest count of boxes that a generator makes. */
|
|
@@ -2378,8 +2263,10 @@ function containerOf(size, w, h, shape) {
|
|
|
2378
2263
|
aspectWeight: 0
|
|
2379
2264
|
}
|
|
2380
2265
|
}[size];
|
|
2381
|
-
|
|
2382
|
-
|
|
2266
|
+
return shape === void 0 ? container : {
|
|
2267
|
+
...container,
|
|
2268
|
+
shape
|
|
2269
|
+
};
|
|
2383
2270
|
}
|
|
2384
2271
|
/** This function gives the 4 corners of a rectangle, clockwise from the top left. */
|
|
2385
2272
|
const corners = (x, y, w, h) => [
|
|
@@ -3071,6 +2958,6 @@ function corpus(name, specs) {
|
|
|
3071
2958
|
inputs
|
|
3072
2959
|
};
|
|
3073
2960
|
}
|
|
3074
|
-
export { DEFAULT_TOLERANCE, EPS, MEASURE_SPECS, RECORD_EPS, Rect, SORT_ORDERS, UnsupportedCell, Vector, aspectError, assertSupported, canonicalJson, cards, cellOf, checkHard, compareRects, corpus, countCell, createContext, dashboard, edgeCases, fnv1a32, fromRects, gallery, generator, generators, hashInput, isDeterministic, logSweep, lowestWindow, marginOf, measure, measureChange, medianRatio, mulberry32, pack, packFree, packRows, packShelf, packSkyline, packWith, packingMetrics, pairedRatio, push, pushCascade, quantile, rectGrid, rectScattered, round3, roundMeasures, rows, shelf, skyline, strategies, strategy, supportsCell, tailShare, toPackInput, toRects, turnItem, turnPoint, turnPort, turnRegion, unsupportedOf, wordCloud };
|
|
2961
|
+
export { DEFAULT_TOLERANCE, EPS, MEASURE_SPECS, RECORD_EPS, Rect, SORT_ORDERS, UnsupportedCell, Vector, aspectError, assertSupported, canonicalJson, cards, cellOf, checkHard, checkInput, compareRects, corpus, countCell, createContext, dashboard, edgeCases, fnv1a32, fromRects, gallery, generator, generators, hashInput, isDeterministic, logSweep, lowestWindow, marginOf, measure, measureChange, medianRatio, mulberry32, pack, packFree, packRows, packShelf, packSkyline, packWith, packingMetrics, pairedRatio, push, pushCascade, quantile, rectGrid, rectScattered, round3, roundMeasures, rows, shelf, skyline, strategies, strategy, supportsCell, tailShare, toPackInput, toRects, turnItem, turnPoint, turnPort, turnRegion, unsupportedOf, wordCloud };
|
|
3075
2962
|
|
|
3076
2963
|
//# sourceMappingURL=index.js.map
|