Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | /**
* Bridges PolylineInfoCanvas (canvas-space polylines + viewReference + holes)
* to the pure-geometry clipperBooleanOps. Groups polylines by viewReference,
* runs Clipper once per group, then flattens back to PolylineInfoCanvas[].
*
* View references must match exactly — operations between polylines on
* different slices/views never combine.
*/
import type { PolylineInfoCanvas } from './polylineInfoTypes';
import { areViewReferencesEqual } from './areViewReferencesEqual';
import {
applyBoolean,
BooleanOp,
type PolygonWithHoles,
} from './clipperBooleanOps';
import type { Types } from '@cornerstonejs/core';
type Group = {
viewReference: PolylineInfoCanvas['viewReference'];
polygons: PolygonWithHoles[];
};
function toPolygon(info: PolylineInfoCanvas): PolygonWithHoles {
return {
outer: info.polyline,
holes: info.holePolylines?.length ? info.holePolylines : undefined,
};
}
function groupByViewReference(set: PolylineInfoCanvas[]): Group[] {
const groups: Group[] = [];
for (const info of set) {
if (info.polyline.length < 3) {
continue;
}
const existing = groups.find((g) =>
areViewReferencesEqual(g.viewReference, info.viewReference)
);
if (existing) {
existing.polygons.push(toPolygon(info));
} else {
groups.push({
viewReference: info.viewReference,
polygons: [toPolygon(info)],
});
}
}
return groups;
}
function flatten(
result: PolygonWithHoles[],
viewReference: PolylineInfoCanvas['viewReference']
): PolylineInfoCanvas[] {
return result.map((p) => ({
polyline: p.outer,
viewReference,
...(p.holes?.length ? { holePolylines: p.holes } : {}),
}));
}
/**
* Run `op` between two PolylineInfoCanvas sets, grouping by view reference so
* polygons on different views never interact.
*
* - For commutative ops (Union, Xor): subject-only and clip-only view groups
* are passed through unchanged.
* - For Difference: subject groups without a matching clip group pass through;
* clip-only groups are dropped (nothing to subtract from).
* - For Intersection: only groups present on both sides contribute.
*/
export function runBooleanOpByView(
setA: PolylineInfoCanvas[],
setB: PolylineInfoCanvas[],
op: BooleanOp
): PolylineInfoCanvas[] {
const aGroups = groupByViewReference(setA);
const bGroups = groupByViewReference(setB);
const out: PolylineInfoCanvas[] = [];
const matchedB = new Set<Group>();
for (const aGroup of aGroups) {
const bGroup = bGroups.find((g) =>
areViewReferencesEqual(g.viewReference, aGroup.viewReference)
);
if (bGroup) {
matchedB.add(bGroup);
const result = applyBoolean(aGroup.polygons, bGroup.polygons, op);
out.push(...flatten(result, aGroup.viewReference));
} else if (op === BooleanOp.Intersection) {
// No matching clip → empty.
} else {
// Union / Xor / Difference with empty clip: subjects unchanged.
out.push(...flatten(aGroup.polygons, aGroup.viewReference));
}
}
// B-only groups: only meaningful for Union and Xor.
if (op === BooleanOp.Union || op === BooleanOp.Xor) {
for (const bGroup of bGroups) {
if (matchedB.has(bGroup)) {
continue;
}
out.push(...flatten(bGroup.polygons, bGroup.viewReference));
}
}
return out;
}
|