我有P1
点的凸多边形N
。这个多边形可以是任何形状或比例(只要它仍然是凸的)。
我需要使用原始多边形几何计算另一个多边形P2
,但需要按给定数量的单位“展开”。算法可以用于扩展凸多边形?
答案 0 :(得分:38)
要展开凸多边形,请绘制与每条边平行的线和给定数量的单位。然后使用新线的交点作为扩展多边形的顶点。最后的javascript / canvas遵循这个功能细分:
第1步:确定哪一方是“出局”
顶点(点)的顺序很重要。在凸多边形中,它们可以按顺时针(CW)或逆时针(CCW)顺序列出。在CW多边形中,将其中一个边缘旋转90度 CCW 以获得向外的法线。在CCW多边形上,改为 CW 。
如果事先不知道顶点的转弯方向,请检查第二条边从第一条边开始转动。在凸多边形中,剩余边缘将继续朝相同方向转动:
Find the CW normal of the first edge。我们还不知道它是向内还是向外。
使用我们计算的法线计算第二条边的dot product。如果第二个边缘变为CW,则点积将为正。否则将是否定的。
数学:
// in vector terms:
v01 = p1 - p0 // first edge, as a vector
v12 = p2 - p1 // second edge, as a vector
n01 = (v01.y, -v01.x) // CW normal of first edge
d = v12 * n01 // dot product
// and in x,y terms:
v01 = (p1.x-p0.x, p1.y-p0.y) // first edge, as a vector
v12 = (p2.x-p1.x, p2.y-p1.y) // second edge, as a vector
n01 = (v01.y, -v01.x) // CW normal of first edge
d = v12.x * n01.x + v12.y * n01.y; // dot product: v12 * n01
if (d > 0) {
// the polygon is CW
} else {
// the polygon is CCW
}
// and what if d==0 ?
// -- that means the second edge continues in the same
// direction as a first. keep looking for an edge that
// actually turns either CW or CCW.
代码:
function vecDot(v1, v2) {
return v1.x * v2.x + v1.y * v2.y;
}
function vecRot90CW(v) {
return { x: v.y, y: -v.x };
}
function vecRot90CCW(v) {
return { x: -v.y, y: v.x };
}
function polyIsCw(p) {
return vecDot(
vecRot90CW({ x: p[1].x - p[0].x, y: p[1].y - p[0].y }),
{ x: p[2].x - p[1].x, y: p[2].y - p[1].y }) >= 0;
}
var rot = polyIsCw(p) ? vecRot90CCW : vecRot90CW;
第2步:找到与多边形边平行的线
现在我们知道哪一侧出来了,我们可以精确地计算与每个多边形边缘平行的线。这是我们的策略:
对于每条边,计算其向外的法线
Normalize正常,使其长度变为一个单位
将法线乘以我们希望扩展多边形来自原始的距离
将乘法法线添加到边的两端。这将在平行线上给出两点。这两点足以定义平行线。
代码:
// given two vertices pt0 and pt1, a desired distance, and a function rot()
// that turns a vector 90 degrees outward:
function vecUnit(v) {
var len = Math.sqrt(v.x * v.x + v.y * v.y);
return { x: v.x / len, y: v.y / len };
}
function vecMul(v, s) {
return { x: v.x * s, y: v.y * s };
}
var v01 = { x: pt1.x - pt0.x, y: pt1.y - pt0.y }; // edge vector
var d01 = vecMul(vecUnit(rot(v01)), distance); // multiplied unit normal
var ptx0 = { x: pt0.x + d01.x, y: pt0.y + d01.y }; // two points on the
var ptx1 = { x: pt1.x + d01.x, y: pt1.y + d01.y }; // parallel line
第3步:计算平行线的交叉点
- 这些将是扩展多边形的顶点。
数学:
通过两点 P1 , P2 的一行可以描述为:
P = P1 + t * (P2 - P1)
两行可以描述为
P = P1 + t * (P2 - P1)
P = P3 + u * (P4 - P3)
他们的交集必须在两条线上:
P = P1 + t * (P2 - P1) = P3 + u * (P4 - P3)
这可以按摩看起来像:
(P2 - P1) * t + (P3 - P4) * u = P3 - P1
以x,y表示的是:
(P2.x - P1.x) * t + (P3.x - P4.x) * u = P3.x - P1.x
(P2.y - P1.y) * t + (P3.y - P4.y) * u = P3.y - P1.y
由于点P1,P2,P3和P4是已知的,因此以下值也是如此:
a1 = P2.x - P1.x a2 = P2.y - P1.y
b1 = P3.x - P4.x b2 = P3.y - P4.y
c1 = P3.x - P1.x c2 = P3.y - P1.y
这将我们的等式缩短为:
a1*t + b1*u = c1
a2*t + b2*u = c2
解决 t 让我们:
t = (b1*c2 - b2*c1)/(a2*b1 - a1*b2)
让我们在P = P1 + t * (P2 - P1)
找到交叉点。
代码:
function intersect(line1, line2) {
var a1 = line1[1].x - line1[0].x;
var b1 = line2[0].x - line2[1].x;
var c1 = line2[0].x - line1[0].x;
var a2 = line1[1].y - line1[0].y;
var b2 = line2[0].y - line2[1].y;
var c2 = line2[0].y - line1[0].y;
var t = (b1*c2 - b2*c1) / (a2*b1 - a1*b2);
return {
x: line1[0].x + t * (line1[1].x - line1[0].x),
y: line1[0].y + t * (line1[1].y - line1[0].y)
};
}
第4步:处理特殊情况
有许多特殊情况需要引起注意。作为练习留给读者......
当两条边之间存在非常尖锐的角度时,展开的顶点可能非常远离原始顶点。如果超出某个阈值,您可能需要考虑剪切扩展边缘。在极端情况下,角度为零,这表明扩展顶点处于无穷大,导致算术中除以零。小心。
当前两条边在同一条线上时,您无法通过查看它们来判断它是CW还是CCW多边形。看看更多的边缘。
非凸多边形更有趣......并且不在这里处理。
完整示例代码
将其放入支持画布的浏览器中。我在Windows上使用了Chrome 6。三角形及其扩展版本应该有效。
<html>
<head>
<style type="text/css">
canvas { border: 1px solid #ccc; }
</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var context = canvas.getContext('2d');
// math for expanding a polygon
function vecUnit(v) {
var len = Math.sqrt(v.x * v.x + v.y * v.y);
return { x: v.x / len, y: v.y / len };
}
function vecMul(v, s) {
return { x: v.x * s, y: v.y * s };
}
function vecDot(v1, v2) {
return v1.x * v2.x + v1.y * v2.y;
}
function vecRot90CW(v) {
return { x: v.y, y: -v.x };
}
function vecRot90CCW(v) {
return { x: -v.y, y: v.x };
}
function intersect(line1, line2) {
var a1 = line1[1].x - line1[0].x;
var b1 = line2[0].x - line2[1].x;
var c1 = line2[0].x - line1[0].x;
var a2 = line1[1].y - line1[0].y;
var b2 = line2[0].y - line2[1].y;
var c2 = line2[0].y - line1[0].y;
var t = (b1*c2 - b2*c1) / (a2*b1 - a1*b2);
return {
x: line1[0].x + t * (line1[1].x - line1[0].x),
y: line1[0].y + t * (line1[1].y - line1[0].y)
};
}
function polyIsCw(p) {
return vecDot(
vecRot90CW({ x: p[1].x - p[0].x, y: p[1].y - p[0].y }),
{ x: p[2].x - p[1].x, y: p[2].y - p[1].y }) >= 0;
}
function expandPoly(p, distance) {
var expanded = [];
var rot = polyIsCw(p) ? vecRot90CCW : vecRot90CW;
for (var i = 0; i < p.length; ++i) {
// get this point (pt1), the point before it
// (pt0) and the point that follows it (pt2)
var pt0 = p[(i > 0) ? i - 1 : p.length - 1];
var pt1 = p[i];
var pt2 = p[(i < p.length - 1) ? i + 1 : 0];
// find the line vectors of the lines going
// into the current point
var v01 = { x: pt1.x - pt0.x, y: pt1.y - pt0.y };
var v12 = { x: pt2.x - pt1.x, y: pt2.y - pt1.y };
// find the normals of the two lines, multiplied
// to the distance that polygon should inflate
var d01 = vecMul(vecUnit(rot(v01)), distance);
var d12 = vecMul(vecUnit(rot(v12)), distance);
// use the normals to find two points on the
// lines parallel to the polygon lines
var ptx0 = { x: pt0.x + d01.x, y: pt0.y + d01.y };
var ptx10 = { x: pt1.x + d01.x, y: pt1.y + d01.y };
var ptx12 = { x: pt1.x + d12.x, y: pt1.y + d12.y };
var ptx2 = { x: pt2.x + d12.x, y: pt2.y + d12.y };
// find the intersection of the two lines, and
// add it to the expanded polygon
expanded.push(intersect([ptx0, ptx10], [ptx12, ptx2]));
}
return expanded;
}
// drawing and animating a sample polygon on a canvas
function drawPoly(p) {
context.beginPath();
context.moveTo(p[0].x, p[0].y);
for (var i = 0; i < p.length; ++i) {
context.lineTo(p[i].x, p[i].y);
}
context.closePath();
context.fill();
context.stroke();
}
function drawPolyWithMargin(p, margin) {
context.fillStyle = "rgb(255,255,255)";
context.strokeStyle = "rgb(200,150,150)";
drawPoly(expandPoly(p, margin));
context.fillStyle = "rgb(150,100,100)";
context.strokeStyle = "rgb(200,150,150)";
drawPoly(p);
}
var p = [{ x: 100, y: 100 }, { x: 200, y: 120 }, { x: 80, y: 200 }];
setInterval(function() {
for (var i in p) {
var pt = p[i];
if (pt.vx === undefined) {
pt.vx = 5 * (Math.random() - 0.5);
pt.vy = 5 * (Math.random() - 0.5);
}
pt.x += pt.vx;
pt.y += pt.vy;
if (pt.x < 0 || pt.x > 400) { pt.vx = -pt.vx; }
if (pt.y < 0 || pt.y > 400) { pt.vy = -pt.vy; }
}
context.clearRect(0, 0, 800, 400);
drawPolyWithMargin(p, 10);
}, 50);
}
});
</script>
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
</body>
</html>
示例代码免责声明:
为了清楚起见,样本牺牲了一些效率。在您的代码中,您可能只想计算每个边的扩展并行一次,而不是在此处计算两次
画布的y坐标向下增长,反转CW / CCW逻辑。事情继续有效,因为我们只需要在与多边形相反的方向上转动向外法线 - 并且两者都被翻转。
答案 1 :(得分:1)
如果多边形以原点为中心,只需将每个点乘以一个公共比例因子即可。
如果多边形不在原点上居中,则首先进行平移,使中心位于原点,缩放,然后将其转换回原点。
发表评论后
您似乎希望所有点都移动距离原点相同的距离。 您可以通过将标准化向量移到此点来为每个点执行此操作。将其乘以“展开常数”并将结果向量添加回原始点。
n.b。如果中心不是此解决方案的起源,您仍需要翻译 - 修改 - 翻译。
答案 2 :(得分:1)
对于原始的每个线段,找到段的中点m和(单位长度)向外的正常u。然后,扩展多边形的相应段将位于通过m + n * u(您希望用n扩展原始值)的线上,使用正常的u。要找到扩展多边形的顶点,您需要找到连续线对的交集。
答案 3 :(得分:0)
让多边形的点为A1,B1,C1 ...现在你有从A1到B1的线,然后从B1到C1 ...我们想要计算多边形P2的点A2,B2,C2。
如果平分角度,例如A1 B1 C1,您将有一条沿您想要的方向前进的线。现在你可以在它上面找到一个B2点,它是距离平分线B1的适当距离。 对多边形P1的所有点重复此操作。
答案 4 :(得分:0)
看直骷髅。正如在这里暗示的那样,非凸多边形存在许多棘手的问题,你可以毫不留情地实现这些问题!