Лабораторная работа номер 5
Всего отрезков
Полностью видимые
Полностью невидимые
Частично видимые
export type LineSegment = {
ax: number;
ay: number;
bx: number;
by: number;
};
export type ClipWindow = {
xmin: number;
ymin: number;
xmax: number;
ymax: number;
};
export type ClippedLineResult = {
visibleParts: LineSegment[];
invisibleParts: LineSegment[];
original: LineSegment;
};
export type ClippingStats = {
totalSegments: number;
fullyVisible: number;
fullyInvisible: number;
partial: number;
timeSeconds: number;
};
export enum ClippingAlgorithm {
COHEN_SUTHERLAND = 'cohen_sutherland',
LIANG_BARSKY = 'liang_barsky',
}
export function drawLine(ctx: CanvasRenderingContext2D, line: LineSegment): void {
ctx.beginPath();
ctx.moveTo(line.ax, line.ay);
ctx.lineTo(line.bx, line.by);
ctx.stroke();
}
export function drawRect(ctx: CanvasRenderingContext2D, rect: ClipWindow): void {
const { xmin, ymin, xmax, ymax } = rect;
ctx.beginPath();
ctx.moveTo(xmin, ymin);
ctx.lineTo(xmax, ymin);
ctx.lineTo(xmax, ymax);
ctx.lineTo(xmin, ymax);
ctx.closePath();
ctx.stroke();
}
function cohenSutherlandClip(line: LineSegment, window: ClipWindow): ClippedLineResult {
const INSIDE = 0;
const LEFT = 1;
const RIGHT = 2;
const BOTTOM = 4;
const TOP = 8;
function computeOutCode(x: number, y: number): number {
let code = INSIDE;
if (x < window.xmin) code |= LEFT;
else if (x > window.xmax) code |= RIGHT;
if (y < window.ymin) code |= BOTTOM;
else if (y > window.ymax) code |= TOP;
return code;
}
let x0 = line.ax;
let y0 = line.ay;
let x1 = line.bx;
let y1 = line.by;
let outCode0 = computeOutCode(x0, y0);
let outCode1 = computeOutCode(x1, y1);
let accept = false;
while (true) {
if (!(outCode0 | outCode1)) {
accept = true;
break;
}
if (outCode0 & outCode1) {
break;
}
const outCodeOut = outCode0 ? outCode0 : outCode1;
let x = 0;
let y = 0;
if (outCodeOut & TOP) {
x = x0 + (x1 - x0) * (window.ymax - y0) / (y1 - y0);
y = window.ymax;
}
else if (outCodeOut & BOTTOM) {
x = x0 + (x1 - x0) * (window.ymin - y0) / (y1 - y0);
y = window.ymin;
}
else if (outCodeOut & RIGHT) {
y = y0 + (y1 - y0) * (window.xmax - x0) / (x1 - x0);
x = window.xmax;
}
else if (outCodeOut & LEFT) {
y = y0 + (y1 - y0) * (window.xmin - x0) / (x1 - x0);
x = window.xmin;
}
if (outCodeOut === outCode0) {
x0 = x;
y0 = y;
outCode0 = computeOutCode(x0, y0);
}
else {
x1 = x;
y1 = y;
outCode1 = computeOutCode(x1, y1);
}
}
const original: LineSegment = { ax: line.ax, ay: line.ay, bx: line.bx, by: line.by };
if (!accept) {
return {
original,
visibleParts: [],
invisibleParts: [original],
};
}
const clipped: LineSegment = { ax: x0, ay: y0, bx: x1, by: y1 };
const invisibleParts: LineSegment[] = [];
if (x0 !== line.ax || y0 !== line.ay) {
invisibleParts.push({ ax: line.ax, ay: line.ay, bx: x0, by: y0 });
}
if (x1 !== line.bx || y1 !== line.by) {
invisibleParts.push({ ax: x1, ay: y1, bx: line.bx, by: line.by });
}
return {
original,
visibleParts: [clipped],
invisibleParts,
};
}
function cyrusBeckClip(line: LineSegment, window: ClipWindow): ClippedLineResult {
const x0 = line.ax;
const y0 = line.ay;
const x1 = line.bx;
const y1 = line.by;
const dx = x1 - x0;
const dy = y1 - y0;
let t0 = 0;
let t1 = 1;
function clipTest(p: number, q: number): boolean {
if (p === 0) {
return q >= 0;
}
const r = q / p;
if (p < 0) {
if (r > t1) return false;
if (r > t0) t0 = r;
}
else if (p > 0) {
if (r < t0) return false;
if (r < t1) t1 = r;
}
return true;
}
if (
clipTest(-dx, x0 - window.xmin)
&& clipTest(dx, window.xmax - x0)
&& clipTest(-dy, y0 - window.ymin)
&& clipTest(dy, window.ymax - y0)
) {
const nx0 = x0 + t0 * dx;
const ny0 = y0 + t0 * dy;
const nx1 = x0 + t1 * dx;
const ny1 = y0 + t1 * dy;
const visible: LineSegment = { ax: nx0, ay: ny0, bx: nx1, by: ny1 };
const original: LineSegment = { ax: line.ax, ay: line.ay, bx: line.bx, by: line.by };
const invisibleParts: LineSegment[] = [];
if (t0 > 0) {
invisibleParts.push({ ax: line.ax, ay: line.ay, bx: nx0, by: ny0 });
}
if (t1 < 1) {
invisibleParts.push({ ax: nx1, ay: ny1, bx: line.bx, by: line.by });
}
return {
original,
visibleParts: [visible],
invisibleParts,
};
}
return {
original: { ax: line.ax, ay: line.ay, bx: line.bx, by: line.by },
visibleParts: [],
invisibleParts: [
{ ax: line.ax, ay: line.ay, bx: line.bx, by: line.by },
],
};
}
export function drawClippingWindow(ctx: CanvasRenderingContext2D): ClipWindow {
const canvas = ctx.canvas;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const margin = 80;
const windowRect: ClipWindow = {
xmin: margin,
ymin: margin,
xmax: canvas.width - margin,
ymax: canvas.height - margin,
};
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
drawRect(ctx, windowRect);
return windowRect;
}
export function clipSegments(
ctx: CanvasRenderingContext2D,
windowRect: ClipWindow,
segments: LineSegment[],
algorithm: ClippingAlgorithm,
): ClippingStats {
const start = performance.now();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
drawRect(ctx, windowRect);
let results: ClippedLineResult[];
if (algorithm === ClippingAlgorithm.LIANG_BARSKY) {
results = segments.map(line => cyrusBeckClip(line, windowRect));
}
else {
results = segments.map(line => cohenSutherlandClip(line, windowRect));
}
ctx.lineWidth = 1;
ctx.strokeStyle = '#999999';
for (const r of results) {
drawLine(ctx, r.original);
}
ctx.lineWidth = 3;
ctx.strokeStyle = 'red';
for (const r of results) {
for (const seg of r.invisibleParts) {
drawLine(ctx, seg);
}
}
ctx.lineWidth = 3;
ctx.strokeStyle = 'green';
for (const r of results) {
for (const seg of r.visibleParts) {
drawLine(ctx, seg);
}
}
let fullyVisible = 0;
let fullyInvisible = 0;
let partial = 0;
for (const r of results) {
if (r.visibleParts.length === 0) {
fullyInvisible++;
}
else if (r.invisibleParts.length === 0) {
fullyVisible++;
}
else {
partial++;
}
}
const timeSeconds = (performance.now() - start) / 1000;
return {
totalSegments: results.length,
fullyVisible,
fullyInvisible,
partial,
timeSeconds,
};
}