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 113 114 115 116 117 118 119 | 128x | import { vec3 } from 'gl-matrix';
import type { Types } from '@cornerstonejs/core';
import liangBarksyClip from '../math/vec2/liangBarksyClip';
import getDisplayedCanvasSize from './getDisplayedCanvasSize';
import type { WorldLine } from './types';
/** Canvas segments shorter than this are treated as degenerate. */
const MIN_SEGMENT_LENGTH = 1e-3;
/**
* Clips an infinite world-space line to the visible canvas of a viewport.
*
* A sufficiently long segment of the line (centered on the point of the line
* closest to the viewport center, to stay numerically well-behaved) is
* converted to canvas space and clipped against the canvas bounds.
*
* Returns the two clipped canvas endpoints, or null when the line does not
* intersect the visible canvas (including the degenerate case where the line
* projects to a single canvas point, e.g. a line perpendicular to the view
* plane).
*/
export default function clipWorldLineToViewportCanvas(
line: WorldLine,
viewport: Types.IVolumeViewport | Types.IViewport
): [Types.Point2, Types.Point2] | null {
if (!line || !viewport) {
return null;
}
const { clientWidth, clientHeight } = getDisplayedCanvasSize(viewport);
if (!clientWidth || !clientHeight) {
return null;
}
const direction = vec3.normalize(vec3.create(), line.direction);
if (vec3.length(direction) < 1e-10) {
return null;
}
// Re-anchor the line on the point closest to the viewport center so the
// projected segment endpoints stay near the canvas even when line.point is
// far away in world space.
const centerWorld = viewport.canvasToWorld([
clientWidth / 2,
clientHeight / 2,
]);
const toCenter = vec3.subtract(vec3.create(), centerWorld, line.point);
const projectedLength = vec3.dot(toCenter, direction);
const anchor = vec3.scaleAndAdd(
vec3.create(),
line.point as Types.Point3,
direction,
projectedLength
);
// Half-length that comfortably covers the canvas: twice the world-space
// distance between the canvas center and a canvas corner.
const cornerWorld = viewport.canvasToWorld([0, 0]);
const halfDiagonalWorld = vec3.distance(centerWorld, cornerWorld);
const halfLength = Math.max(halfDiagonalWorld * 2, 1);
const startWorld = vec3.scaleAndAdd(
vec3.create(),
anchor,
direction,
-halfLength
);
const endWorld = vec3.scaleAndAdd(
vec3.create(),
anchor,
direction,
halfLength
);
const startCanvas = viewport.worldToCanvas([
startWorld[0],
startWorld[1],
startWorld[2],
]);
const endCanvas = viewport.worldToCanvas([
endWorld[0],
endWorld[1],
endWorld[2],
]);
if (
![...startCanvas, ...endCanvas].every((component) =>
Number.isFinite(component)
)
) {
return null;
}
const clippedStart: Types.Point2 = [startCanvas[0], startCanvas[1]];
const clippedEnd: Types.Point2 = [endCanvas[0], endCanvas[1]];
const inside = liangBarksyClip(clippedStart, clippedEnd, [
0,
0,
clientWidth,
clientHeight,
]);
if (inside !== 1) {
return null;
}
const segmentLength = Math.hypot(
clippedEnd[0] - clippedStart[0],
clippedEnd[1] - clippedStart[1]
);
if (segmentLength < MIN_SEGMENT_LENGTH) {
return null;
}
return [clippedStart, clippedEnd];
}
|