如何剪裁多边形
该问题的通用解决方案是使用裁剪算法。
多边形是一组连接的点,剪裁它意味着使用一条线删除多边形的一部分,沿着剪裁线创建新点。
图像将有助于可视化该过程。
我们从一个有 6 个边和 6 个顶点的多边形开始 [1,2,3,4,5,6]。然后我们定义一条剪切线A,B。这条线实际上是无限长的,但我们需要两个点来定义它,这些点可以在线上的任何位置,但方向很重要。我们删除了右边的部分,保留了左边的部分。
一些辅助函数
创建点、线和多边形的函数
function point(x = 0, y = 0) { return {x,y} }
function line(p1 = point(), p2 = point()) { return {p1, p2} }
function polygon(...points) { return {points : [...points]} }
现在我们可以创建一个多边形
const poly1 = polygon(point(10,100), point(40,50), point(100,50), point(150,90), point(80,200), point(30,160));
还有一条要剪下来的线
const clipLine = line(point(100,0), point(150,220));
剪辑
为了剪裁多边形,我们首先创建一个空多边形,其中将包含剪裁后的多边形。
const clippedPoly = polygon();
是线的左边还是右边
然后我们从第一个点开始检查该点是在剪切线的左侧还是右侧,如果是,则将该点添加到新的多边形中。
我们需要一个函数来检查一个点是在一条线的左边还是右边。
function isPointLeftOfLine(point, line){
const x1 = line.p2.x - line.p1.x;
const y1 = line.p2.y - line.p1.y;
const x2 = point.x - line.p1.x;
const y2 = point.y - line.p1.y;
return x1 * y2 - y1 * x2 > 0; // if the cross product is positive then left of line
}
我们继续沿着多边形点向新多边形添加点,直到我们找到在线右侧的点。
两条线相交的点
当我们找到那个点时,我们知道在沿线的某个点,这条线被剪切线剪断了。所以我们需要找到那个点。
function lineLineIntercept(l1, l2, p = point()){
const x1 = l1.p2.x - l1.p1.x;
const y1 = l1.p2.y - l1.p1.y;
const x2 = l2.p2.x - l2.p1.x;
const y2 = l2.p2.y - l2.p1.y;
const cross = x1 * y2 - y1 * x2;
const u = (x2 * (l1.p1.y - l2.p1.y) - y2 * (l1.p1.x - l2.p1.x)) / cross;
p.x = l1.p1.x + x1 * u;
p.y = l1.p1.y + y1 * u;
return p;
}
我们将新点添加到新多边形并继续检查点。我们发现的每个点都在我们忽略的线的右边,然后继续下一个。
当我们再次找到线左侧的点时,我们知道我们有另一条多边形线被剪辑切割。所以我们找到截距并将其添加到新的多边形中。
我们继续这个过程,直到没有更多的分数。生成的多边形已被剪裁到 A,B 线
到矩形
因此,对于 OP,您将矩形创建为一组四个点。然后以您想要的角度和位置创建第一条剪切线。使用上述过程剪切矩形。然后在您想要的角度和位置创建第二条剪切线,并剪切上一个剪辑的结果。
请记住,剪切线的方向很重要。在您的情况下,第二条剪裁线必须与第一条剪裁线的方向相反。
裁剪多边形示例。
使用上述方法进行多边形裁剪的交互式示例。
canvas.width = 300;
canvas.height = 300;
const ctx = canvas.getContext("2d");
function point(x = 0, y = 0) { return { x, y } }
function line(p1 = point(), p2 = point()) { return { p1, p2 } }
function polygon(...points) { return { points : [...points] } }
function isPointLeftOfLine(point, line) {
const x1 = line.p2.x - line.p1.x;
const y1 = line.p2.y - line.p1.y;
const x2 = point.x - line.p1.x;
const y2 = point.y - line.p1.y;
return x1 * y2 - y1 * x2 > 0; // if the cross product is positive then left of line
}
function lineLineIntercept(l1, l2, p = point()) {
const x1 = l1.p2.x - l1.p1.x;
const y1 = l1.p2.y - l1.p1.y;
const x2 = l2.p2.x - l2.p1.x;
const y2 = l2.p2.y - l2.p1.y;
const cross = x1 * y2 - y1 * x2;
const u = (x2 * (l1.p1.y - l2.p1.y) - y2 * (l1.p1.x - l2.p1.x)) / cross;
p.x = l1.p1.x + x1 * u;
p.y = l1.p1.y + y1 * u;
return p;
}
function clipPolygon(poly, cLine, result = polygon()) {
poly.points.push(poly.points[0]); // make copy of last point to simplify code
result.points.length = 0;
var lastPointLeft = true;
poly.points.forEach((point, index) => {
if (isPointLeftOfLine(point,cLine)) {
if (!lastPointLeft) {
result.points.push(lineLineIntercept(line(poly.points[index - 1], point), cLine));
}
if (index < poly.points.length - 1) {
result.points.push(point);
}
lastPointLeft = true;
} else {
if (index > 0 && lastPointLeft) {
result.points.push(lineLineIntercept(line(poly.points[index - 1], point), cLine));
}
lastPointLeft = false;
}
});
poly.points.pop(); // remove last point
return result;
}
const poly1 = polygon(
point(10, 100),
point(40, 50),
point(100, 50),
point(150, 90),
point(250, 20),
point(200, 120),
point(100, 120),
point(250, 220),
point(80, 200),
point(30, 160)
);
const clipLine = line(point(150, 0), point(150, 220));
const clipLine2 = line(point(50, 300), point(50, 0));
var clippedPoly = polygon();
var clippedPoly2 = polygon();
const mouse = clipLine.p2;
function mouseEvents(e) {
const m = mouse;
const bounds = canvas.getBoundingClientRect();
m.x = e.pageX - bounds.left - scrollX;
m.y = e.pageY - bounds.top - scrollY;
}
document.addEventListener("mousemove", mouseEvents);
function drawLine(line) {
ctx.beginPath();
ctx.lineTo(line.p1.x, line.p1.y);
ctx.lineTo(line.p2.x, line.p2.y);
ctx.stroke();
}
function drawPoint(point) {
ctx.fillRect(point.x - 3, point.y - 3, 6, 6);
}
function drawPoly(polygon) {
ctx.beginPath();
var i = 0;
while (i < polygon.points.length) {
ctx.lineTo(polygon.points[i].x, polygon.points[i++].y);
}
ctx.closePath();
ctx.stroke();
}
function drawPolyPoints(polygon) {
var i = 0;
while (i < polygon.points.length) {
drawPoint(polygon.points[i++]);
}
}
var w = canvas.width;
var h = canvas.height;
ctx.fillStyle = "black";
ctx.lineJoin = "round";
ctx.lineWidth = 2;
function update() {
ctx.clearRect(0, 0, w, h);
ctx.strokeStyle = "#FAA";
drawPoly(poly1);
ctx.strokeStyle = "blue";
drawLine(clipLine);
drawPoint(clipLine.p1);
drawPoint(clipLine.p2);
drawLine(clipLine2);
drawPoint(clipLine2.p1);
drawPoint(clipLine2.p2);
clippedPoly2 = clipPolygon(poly1, clipLine, clippedPoly2);
if(clippedPoly2.points.length > 0){
clippedPoly = clipPolygon(clippedPoly2, clipLine2, clippedPoly);
ctx.strokeStyle = "#F00";
drawPoly(clippedPoly);
ctx.fillStyle = "#D62";
ctx.globalAlpha = 0.2;
ctx.fill();
ctx.globalAlpha = 1;
ctx.fillStyle = "black";
drawPolyPoints(clippedPoly);
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
border: 2px solid black;
}
<canvas id="canvas"></canvas>