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 | 609x 216x 37226x 37226x 74452x 58266x 37226x 6315x 30911x 30911x 30911x 27355x 27355x 27345x 27345x 10x 10x 10x 30911x 393x 393x 786x 786x | import type { Point2 } from '../../../types';
import type { Point3 } from '../../../types';
import type { IndexSpaceSlab } from '../indexSpaceSlab';
import type { VoxelSlabShape } from './shapeGeometry';
/**
* The union of several plane-anchored shapes, as one shape.
*
* A brush that the user drags paints the union of one disc per sample along the
* stroke, and a single shape cannot describe that. Every member must share the
* annotation plane and the normal that the iterator is built with, because the
* runs of all members are emitted along one column axis.
*
* The runs stay exact. Each member's runs are merged into a disjoint, ascending
* sequence, so `iterateVoxelsInShape` visits a voxel that two members share
* exactly once - which matters, because a fill that writes a voxel twice
* records two undo entries for it.
*
* Cost is proportional to the number of members per row, not per voxel. A
* per-voxel `containsPoint` over the same members costs the member count for
* every voxel of the bounding box instead.
*
* See `docs/docs/concepts/cornerstone-tools/annotation/voxel-statistics.md`.
*
* @param shapes - The members. An empty list gives a shape that contains
* nothing, which lets a caller build a union without a special case for a
* stroke that recorded no points.
*/
export function createUnionShape(shapes: VoxelSlabShape[]): VoxelSlabShape {
// One member is its own union, and the merge below would only add a layer of
// generators to every row.
if (shapes.length === 1) {
return shapes[0];
}
function containsPoint(point: Point3): boolean {
return shapes.some((shape) => shape.containsPoint(point));
}
function* getRuns(
outerIndex: number,
rowIndex: number,
depthRun: Point2,
slab: IndexSpaceSlab
): Generator<Point2, void, undefined> {
const runs: Point2[] = [];
for (const shape of shapes) {
for (const run of shape.getRuns(outerIndex, rowIndex, depthRun, slab)) {
// The member reuses no run array in any shape here, but copy anyway:
// the merge below holds every run until the row is complete, and a
// future member that reuses one would corrupt the whole row.
runs.push([run[0], run[1]]);
}
}
if (!runs.length) {
return;
}
runs.sort((a, b) => a[0] - b[0]);
let [low, high] = runs[0];
for (let index = 1; index < runs.length; index++) {
const [nextLow, nextHigh] = runs[index];
// `high + 1` merges runs that only touch, so two discs that meet at a
// voxel boundary yield one run rather than two.
if (nextLow <= high + 1) {
high = Math.max(high, nextHigh);
continue;
}
yield [low, high];
low = nextLow;
high = nextHigh;
}
yield [low, high];
}
return {
containsPoint,
getRuns,
// The slab must hold every member, so the deepest one sets the thickness.
// A union of planar shapes reports 0, and the caller's own thickness stands.
getRequiredThickness: () =>
shapes.reduce((deepest, shape) => {
const required = shape.getRequiredThickness();
return required > deepest ? required : deepest;
}, 0),
};
}
|