Drag the polygon (blue) across the clipping window (red dashed rectangle) and watch the algorithm trim it in real time. Use Step to advance one clipping edge at a time, or Run All to see the final result. The green shape is what survives after all four edges have clipped it.
<p class="hint">Drag the blue polygon across the red clipping window. Use <b>Step</b> to clip one edge at a time, or <b>Run All</b> to see the final result. The green shape is what survives.</p>
<canvas id="canvas" width="480" height="340"></canvas>
<div class="controls">
<button id="step" type="button">Step (edge <span id="edgeNum">1</span>/4)</button>
<button id="runAll" type="button">Run All</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="info" id="info">Drag the polygon, then step through the algorithm.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 10px;
background: #f6f8fa; cursor: grab; width: 100%; max-width: 480px; touch-action: none; }
canvas.dragging { cursor: grabbing; }
.controls { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .6rem; }
button { font: 600 13px system-ui, sans-serif; padding: .42rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.info { font-size: .88rem; font-weight: 600; margin-top: .5rem; min-height: 1.3em; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// โโ Sutherland-Hodgman polygon clipping demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Clips a subject polygon against a convex rectangular window, one edge at a
// time. The algorithm runs in O(n * k) where n = polygon vertices, k = clip edges.
//
// Clipping window (clip space coords on the 480ร340 canvas):
const WIN = { x: 130, y: 70, w: 220, h: 200 };
const CLIP_EDGES = [
// Each edge = {nx,ny,d}: inside half-plane is nx*x + ny*y <= d
{ nx: -1, ny: 0, d: -WIN.x }, // left x >= WIN.x
{ nx: 1, ny: 0, d: WIN.x + WIN.w }, // right x <= WIN.x+WIN.w
{ nx: 0, ny: -1, d: -WIN.y }, // top y >= WIN.y
{ nx: 0, ny: 1, d: WIN.y + WIN.h }, // bottom y <= WIN.y+WIN.h
];
const EDGE_NAMES = ['left', 'right', 'top', 'bottom'];
const COLORS = ['#e07b39','#e0c039','#9b59b6','#27ae60'];
// โโ initial subject polygon (pentagon, centered on canvas) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const INIT_POLY = [
{ x: 240, y: 60 },
{ x: 320, y: 130 },
{ x: 290, y: 230 },
{ x: 190, y: 230 },
{ x: 160, y: 130 },
];
let poly = INIT_POLY.map(p => ({ ...p })); // current clipped polygon per step
let basePoly = INIT_POLY.map(p => ({ ...p })); // drag-offset base
let currentStep = 0; // how many edges have been applied (0โ4)
let stages = [null, null, null, null]; // result after each edge
// โโ drag state โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let dragging = false, dragStart = null, polyAtDrag = null;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function canvasPoint(e) {
const r = canvas.getBoundingClientRect();
const scaleX = canvas.width / r.width;
const scaleY = canvas.height / r.height;
const src = e.touches ? e.touches[0] : e;
return { x: (src.clientX - r.left) * scaleX, y: (src.clientY - r.top) * scaleY };
}
function onPointerDown(e) {
dragging = true; dragStart = canvasPoint(e);
polyAtDrag = basePoly.map(p => ({ ...p }));
canvas.classList.add('dragging'); e.preventDefault();
}
function onPointerMove(e) {
if (!dragging) return;
const pt = canvasPoint(e);
const dx = pt.x - dragStart.x, dy = pt.y - dragStart.y;
basePoly = polyAtDrag.map(p => ({ x: p.x + dx, y: p.y + dy }));
resetClip(); e.preventDefault();
}
function onPointerUp() { dragging = false; canvas.classList.remove('dragging'); }
canvas.addEventListener('mousedown', onPointerDown);
canvas.addEventListener('mousemove', onPointerMove);
canvas.addEventListener('mouseup', onPointerUp);
canvas.addEventListener('touchstart', onPointerDown, { passive: false });
canvas.addEventListener('touchmove', onPointerMove, { passive: false });
canvas.addEventListener('touchend', onPointerUp);
// โโ Sutherland-Hodgman core โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function inside(p, e) { return e.nx * p.x + e.ny * p.y <= e.d; }
function intersect(a, b, e) {
const dx = b.x - a.x, dy = b.y - a.y;
const denom = e.nx * dx + e.ny * dy;
if (Math.abs(denom) < 1e-9) return a; // parallel
const t = (e.d - e.nx * a.x - e.ny * a.y) / denom;
return { x: a.x + t * dx, y: a.y + t * dy };
}
function clipAgainstEdge(polygon, edge) {
if (polygon.length === 0) return [];
const out = [];
for (let i = 0; i < polygon.length; i++) {
const curr = polygon[i];
const prev = polygon[(i + polygon.length - 1) % polygon.length];
const currIn = inside(curr, edge);
const prevIn = inside(prev, edge);
if (currIn) {
if (!prevIn) out.push(intersect(prev, curr, edge));
out.push(curr);
} else if (prevIn) {
out.push(intersect(prev, curr, edge));
}
}
return out;
}
function resetClip() {
poly = basePoly.map(p => ({ ...p }));
currentStep = 0;
stages = [null, null, null, null];
document.getElementById('edgeNum').textContent = '1';
setInfo('Drag the polygon, then step through the algorithm.');
draw();
}
function stepClip() {
if (currentStep >= 4) { setInfo('All 4 edges applied โ clipping complete.'); draw(); return; }
const result = clipAgainstEdge(poly, CLIP_EDGES[currentStep]);
stages[currentStep] = result;
poly = result;
currentStep++;
document.getElementById('edgeNum').textContent = Math.min(currentStep + 1, 4);
if (poly.length === 0) {
setInfo('Polygon is entirely outside the window โ nothing remains.');
} else {
setInfo(fmt('Clipped against %v% edge (%currentStep%/4). %v2%', { v: EDGE_NAMES[currentStep - 1], currentStep: currentStep, v2: currentStep < 4 ? 'Press Step for next edge.' : 'Done!' }));
}
draw();
}
function runAll() {
poly = basePoly.map(p => ({ ...p }));
currentStep = 0; stages = [null, null, null, null];
for (let i = 0; i < 4; i++) {
stages[i] = clipAgainstEdge(poly, CLIP_EDGES[i]);
poly = stages[i];
currentStep++;
}
document.getElementById('edgeNum').textContent = '4';
if (poly.length === 0) setInfo('Polygon is entirely outside โ nothing survives.');
else setInfo(fmt('All 4 edges applied. %length% vertices in the clipped polygon.', { length: poly.length }));
draw();
}
function setInfo(t) { document.getElementById('info').textContent = t; }
// โโ drawing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function drawPoly(pts, fillStyle, strokeStyle, alpha) {
if (pts.length < 2) return;
ctx.globalAlpha = alpha;
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
pts.slice(1).forEach(p => ctx.lineTo(p.x, p.y));
ctx.closePath();
ctx.fillStyle = fillStyle; ctx.fill();
ctx.strokeStyle = strokeStyle; ctx.lineWidth = 2; ctx.stroke();
ctx.globalAlpha = 1;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// โโ clipping window โโ
ctx.setLineDash([6, 4]);
ctx.strokeStyle = '#c0392b'; ctx.lineWidth = 2;
ctx.strokeRect(WIN.x, WIN.y, WIN.w, WIN.h);
ctx.setLineDash([]);
ctx.fillStyle = 'rgba(192,57,43,0.04)';
ctx.fillRect(WIN.x, WIN.y, WIN.w, WIN.h);
ctx.fillStyle = '#c0392b'; ctx.font = '11px system-ui'; ctx.fillText('Clip window', WIN.x + 4, WIN.y - 6);
// โโ intermediate stage outlines โโ
for (let i = 0; i < currentStep - 1; i++) {
if (stages[i] && stages[i].length > 1) {
ctx.globalAlpha = 0.22;
ctx.beginPath();
ctx.moveTo(stages[i][0].x, stages[i][0].y);
stages[i].slice(1).forEach(p => ctx.lineTo(p.x, p.y));
ctx.closePath();
ctx.strokeStyle = COLORS[i]; ctx.lineWidth = 1.5; ctx.stroke();
ctx.globalAlpha = 1;
}
}
// โโ original polygon โโ
drawPoly(basePoly, 'rgba(52,100,173,0.18)', '#3464ad', 1);
// dots on original vertices
basePoly.forEach(p => {
ctx.beginPath(); ctx.arc(p.x, p.y, 3.5, 0, 2 * Math.PI);
ctx.fillStyle = '#3464ad'; ctx.fill();
});
// โโ clipped result โโ
if (currentStep > 0 && poly.length > 1) {
drawPoly(poly, 'rgba(39,174,96,0.32)', '#1a8a52', 1);
poly.forEach(p => {
ctx.beginPath(); ctx.arc(p.x, p.y, 3.5, 0, 2 * Math.PI);
ctx.fillStyle = '#1a8a52'; ctx.fill();
});
}
// โโ edge being clipped (current step indicator) โโ
if (currentStep < 4) {
const e = CLIP_EDGES[currentStep];
ctx.save();
ctx.strokeStyle = COLORS[currentStep]; ctx.lineWidth = 2.5; ctx.setLineDash([4, 3]);
ctx.beginPath();
if (e.nx !== 0) { const x = -e.d / e.nx; ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); }
else { const y = -e.d / e.ny; ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); }
ctx.stroke(); ctx.setLineDash([]); ctx.restore();
ctx.fillStyle = COLORS[currentStep]; ctx.font = 'bold 11px system-ui';
if (e.nx !== 0) ctx.fillText(fmt('next: %v%', { v: EDGE_NAMES[currentStep] }), -e.d / e.nx + 4, 20);
else ctx.fillText(fmt('next: %v%', { v: EDGE_NAMES[currentStep] }), 4, -e.d / e.ny - 6);
}
}
document.getElementById('step').addEventListener('click', stepClip);
document.getElementById('runAll').addEventListener('click', runAll);
document.getElementById('reset').addEventListener('click', resetClip);
draw();
// โโ verification (runs silently, outputs to console) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
(function verify() {
// Clip the test pentagon fully and check vertex count & containment
let p = INIT_POLY.map(v => ({ ...v }));
for (const e of CLIP_EDGES) p = clipAgainstEdge(p, e);
const allInside = p.every(v =>
v.x >= WIN.x - 0.5 && v.x <= WIN.x + WIN.w + 0.5 &&
v.y >= WIN.y - 0.5 && v.y <= WIN.y + WIN.h + 0.5
);
console.assert(p.length === 5, fmt('expected 5 clipped verts, got %length%', { length: p.length }));
console.assert(allInside, 'some clipped vertex is outside the window');
console.log(fmt('SH verify: %length% verts, allInside=%allInside%', { length: p.length, allInside: allInside }));
})();
<p class="hint">Drag the blue polygon across the red clipping window. Use <b>Step</b> to clip one edge at a time, or <b>Run All</b> to see the final result. The green shape is what survives.</p>
<canvas id="canvas" width="480" height="340"></canvas>
<div class="controls">
<button id="step" type="button">Step (edge <span id="edgeNum">1</span>/4)</button>
<button id="runAll" type="button">Run All</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="info" id="info">Drag the polygon, then step through the algorithm.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 10px;
background: #f6f8fa; cursor: grab; width: 100%; max-width: 480px; touch-action: none; }
canvas.dragging { cursor: grabbing; }
.controls { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .6rem; }
button { font: 600 13px system-ui, sans-serif; padding: .42rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.info { font-size: .88rem; font-weight: 600; margin-top: .5rem; min-height: 1.3em; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// โโ Sutherland-Hodgman polygon clipping demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Clips a subject polygon against a convex rectangular window, one edge at a
// time. The algorithm runs in O(n * k) where n = polygon vertices, k = clip edges.
//
// Clipping window (clip space coords on the 480ร340 canvas):
const WIN = { x: 130, y: 70, w: 220, h: 200 };
const CLIP_EDGES = [
// Each edge = {nx,ny,d}: inside half-plane is nx*x + ny*y <= d
{ nx: -1, ny: 0, d: -WIN.x }, // left x >= WIN.x
{ nx: 1, ny: 0, d: WIN.x + WIN.w }, // right x <= WIN.x+WIN.w
{ nx: 0, ny: -1, d: -WIN.y }, // top y >= WIN.y
{ nx: 0, ny: 1, d: WIN.y + WIN.h }, // bottom y <= WIN.y+WIN.h
];
const EDGE_NAMES = ['left', 'right', 'top', 'bottom'];
const COLORS = ['#e07b39','#e0c039','#9b59b6','#27ae60'];
// โโ initial subject polygon (pentagon, centered on canvas) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const INIT_POLY = [
{ x: 240, y: 60 },
{ x: 320, y: 130 },
{ x: 290, y: 230 },
{ x: 190, y: 230 },
{ x: 160, y: 130 },
];
let poly = INIT_POLY.map(p => ({ ...p })); // current clipped polygon per step
let basePoly = INIT_POLY.map(p => ({ ...p })); // drag-offset base
let currentStep = 0; // how many edges have been applied (0โ4)
let stages = [null, null, null, null]; // result after each edge
// โโ drag state โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let dragging = false, dragStart = null, polyAtDrag = null;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function canvasPoint(e) {
const r = canvas.getBoundingClientRect();
const scaleX = canvas.width / r.width;
const scaleY = canvas.height / r.height;
const src = e.touches ? e.touches[0] : e;
return { x: (src.clientX - r.left) * scaleX, y: (src.clientY - r.top) * scaleY };
}
function onPointerDown(e) {
dragging = true; dragStart = canvasPoint(e);
polyAtDrag = basePoly.map(p => ({ ...p }));
canvas.classList.add('dragging'); e.preventDefault();
}
function onPointerMove(e) {
if (!dragging) return;
const pt = canvasPoint(e);
const dx = pt.x - dragStart.x, dy = pt.y - dragStart.y;
basePoly = polyAtDrag.map(p => ({ x: p.x + dx, y: p.y + dy }));
resetClip(); e.preventDefault();
}
function onPointerUp() { dragging = false; canvas.classList.remove('dragging'); }
canvas.addEventListener('mousedown', onPointerDown);
canvas.addEventListener('mousemove', onPointerMove);
canvas.addEventListener('mouseup', onPointerUp);
canvas.addEventListener('touchstart', onPointerDown, { passive: false });
canvas.addEventListener('touchmove', onPointerMove, { passive: false });
canvas.addEventListener('touchend', onPointerUp);
// โโ Sutherland-Hodgman core โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function inside(p, e) { return e.nx * p.x + e.ny * p.y <= e.d; }
function intersect(a, b, e) {
const dx = b.x - a.x, dy = b.y - a.y;
const denom = e.nx * dx + e.ny * dy;
if (Math.abs(denom) < 1e-9) return a; // parallel
const t = (e.d - e.nx * a.x - e.ny * a.y) / denom;
return { x: a.x + t * dx, y: a.y + t * dy };
}
function clipAgainstEdge(polygon, edge) {
if (polygon.length === 0) return [];
const out = [];
for (let i = 0; i < polygon.length; i++) {
const curr = polygon[i];
const prev = polygon[(i + polygon.length - 1) % polygon.length];
const currIn = inside(curr, edge);
const prevIn = inside(prev, edge);
if (currIn) {
if (!prevIn) out.push(intersect(prev, curr, edge));
out.push(curr);
} else if (prevIn) {
out.push(intersect(prev, curr, edge));
}
}
return out;
}
function resetClip() {
poly = basePoly.map(p => ({ ...p }));
currentStep = 0;
stages = [null, null, null, null];
document.getElementById('edgeNum').textContent = '1';
setInfo('Drag the polygon, then step through the algorithm.');
draw();
}
function stepClip() {
if (currentStep >= 4) { setInfo('All 4 edges applied โ clipping complete.'); draw(); return; }
const result = clipAgainstEdge(poly, CLIP_EDGES[currentStep]);
stages[currentStep] = result;
poly = result;
currentStep++;
document.getElementById('edgeNum').textContent = Math.min(currentStep + 1, 4);
if (poly.length === 0) {
setInfo('Polygon is entirely outside the window โ nothing remains.');
} else {
setInfo(fmt('Clipped against %v% edge (%currentStep%/4). %v2%', { v: EDGE_NAMES[currentStep - 1], currentStep: currentStep, v2: currentStep < 4 ? 'Press Step for next edge.' : 'Done!' }));
}
draw();
}
function runAll() {
poly = basePoly.map(p => ({ ...p }));
currentStep = 0; stages = [null, null, null, null];
for (let i = 0; i < 4; i++) {
stages[i] = clipAgainstEdge(poly, CLIP_EDGES[i]);
poly = stages[i];
currentStep++;
}
document.getElementById('edgeNum').textContent = '4';
if (poly.length === 0) setInfo('Polygon is entirely outside โ nothing survives.');
else setInfo(fmt('All 4 edges applied. %length% vertices in the clipped polygon.', { length: poly.length }));
draw();
}
function setInfo(t) { document.getElementById('info').textContent = t; }
// โโ drawing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function drawPoly(pts, fillStyle, strokeStyle, alpha) {
if (pts.length < 2) return;
ctx.globalAlpha = alpha;
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
pts.slice(1).forEach(p => ctx.lineTo(p.x, p.y));
ctx.closePath();
ctx.fillStyle = fillStyle; ctx.fill();
ctx.strokeStyle = strokeStyle; ctx.lineWidth = 2; ctx.stroke();
ctx.globalAlpha = 1;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// โโ clipping window โโ
ctx.setLineDash([6, 4]);
ctx.strokeStyle = '#c0392b'; ctx.lineWidth = 2;
ctx.strokeRect(WIN.x, WIN.y, WIN.w, WIN.h);
ctx.setLineDash([]);
ctx.fillStyle = 'rgba(192,57,43,0.04)';
ctx.fillRect(WIN.x, WIN.y, WIN.w, WIN.h);
ctx.fillStyle = '#c0392b'; ctx.font = '11px system-ui'; ctx.fillText('Clip window', WIN.x + 4, WIN.y - 6);
// โโ intermediate stage outlines โโ
for (let i = 0; i < currentStep - 1; i++) {
if (stages[i] && stages[i].length > 1) {
ctx.globalAlpha = 0.22;
ctx.beginPath();
ctx.moveTo(stages[i][0].x, stages[i][0].y);
stages[i].slice(1).forEach(p => ctx.lineTo(p.x, p.y));
ctx.closePath();
ctx.strokeStyle = COLORS[i]; ctx.lineWidth = 1.5; ctx.stroke();
ctx.globalAlpha = 1;
}
}
// โโ original polygon โโ
drawPoly(basePoly, 'rgba(52,100,173,0.18)', '#3464ad', 1);
// dots on original vertices
basePoly.forEach(p => {
ctx.beginPath(); ctx.arc(p.x, p.y, 3.5, 0, 2 * Math.PI);
ctx.fillStyle = '#3464ad'; ctx.fill();
});
// โโ clipped result โโ
if (currentStep > 0 && poly.length > 1) {
drawPoly(poly, 'rgba(39,174,96,0.32)', '#1a8a52', 1);
poly.forEach(p => {
ctx.beginPath(); ctx.arc(p.x, p.y, 3.5, 0, 2 * Math.PI);
ctx.fillStyle = '#1a8a52'; ctx.fill();
});
}
// โโ edge being clipped (current step indicator) โโ
if (currentStep < 4) {
const e = CLIP_EDGES[currentStep];
ctx.save();
ctx.strokeStyle = COLORS[currentStep]; ctx.lineWidth = 2.5; ctx.setLineDash([4, 3]);
ctx.beginPath();
if (e.nx !== 0) { const x = -e.d / e.nx; ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); }
else { const y = -e.d / e.ny; ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); }
ctx.stroke(); ctx.setLineDash([]); ctx.restore();
ctx.fillStyle = COLORS[currentStep]; ctx.font = 'bold 11px system-ui';
if (e.nx !== 0) ctx.fillText(fmt('next: %v%', { v: EDGE_NAMES[currentStep] }), -e.d / e.nx + 4, 20);
else ctx.fillText(fmt('next: %v%', { v: EDGE_NAMES[currentStep] }), 4, -e.d / e.ny - 6);
}
}
document.getElementById('step').addEventListener('click', stepClip);
document.getElementById('runAll').addEventListener('click', runAll);
document.getElementById('reset').addEventListener('click', resetClip);
draw();
// โโ verification (runs silently, outputs to console) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
(function verify() {
// Clip the test pentagon fully and check vertex count & containment
let p = INIT_POLY.map(v => ({ ...v }));
for (const e of CLIP_EDGES) p = clipAgainstEdge(p, e);
const allInside = p.every(v =>
v.x >= WIN.x - 0.5 && v.x <= WIN.x + WIN.w + 0.5 &&
v.y >= WIN.y - 0.5 && v.y <= WIN.y + WIN.h + 0.5
);
console.assert(p.length === 5, fmt('expected 5 clipped verts, got %length%', { length: p.length }));
console.assert(allInside, 'some clipped vertex is outside the window');
console.log(fmt('SH verify: %length% verts, allInside=%allInside%', { length: p.length, allInside: allInside }));
})();
Notice how each edge processes the polygon independently: vertices inside the boundary are kept, vertices outside are discarded, and a new intersection point is inserted wherever an edge crosses the boundary. The algorithm runs in O(nโ
k) time where n is the number of polygon vertices and k is the number of clipping edges โ linear in the size of the input for any fixed window shape.
Comments
Loading comments...