碰撞检测:分离轴定理 - 圆与多边形
Collision detection: Separating Axis Theorem - Circle versus Polygon
我一直在尝试基于 Randy Gaul's C++ Impulse Engine 实现圆和多边形之间的碰撞检测,非常严格地遵循代码,但算法从来没有 returns 正确。
这是 JSFiddle。 (为方便起见,身体使用 HTML5 Canvas API 渲染)
一段代码(只是碰撞检测):
const circPoly = (a, b) => {
let data = {},
center = a.pos;
data.contacts = [];
center = b.mat.clone().trans().mult(center.clone().sub(b.pos));
let sep = -Number.MAX_VALUE,
faceNorm = 0;
for (let i = 0; i < b.verts2.length; ++i) {
let sep2 = b.norms[i].dot(center.clone().sub(b.verts2[i]));
if (sep2 > a.radius) return data;
if (sep2 > sep) { sep = sep2; faceNorm = i; }
}
let v1 = b.verts2[faceNorm],
v2 = b.verts2[faceNorm + 1 < b.verts2.length ? faceNorm + 1 : 0];
if (sep < 0.0001) {
data.depth = a.radius;
data.norm = b.mat.clone().mult(b.norms[faceNorm]).neg();
data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius));
return data;
}
let dot1 = center.clone().sub(v1).dot(v2.clone().sub(v1)),
dot2 = center.clone().sub(v2).dot(v1.clone().sub(v2));
data.depth = a.radius - sep;
if (dot1 <= 0) {
if (center.dist2(v1) > a.radius * a.radius) return data;
let norm = v1.clone().sub(center);
norm = b.mat.clone().mult(norm);
norm.norm();
data.norm = norm;
v1 = b.mat.clone().mult(v1.clone().add(b.pos));
data.contacts[0] = v1;
} else if (dot2 <= 0) {
if (center.dist2(v2) > a.radius * a.radius) return data;
let norm = v2.clone().sub(center);
norm = b.mat.clone().mult(norm);
norm.norm();
data.norm = norm;
v2 = b.mat.clone().mult(v2.clone().add(b.pos));
data.contacts[0] = v2;
} else {
let norm = b.norms[faceNorm];
if (center.clone().sub(v1).dot(norm) > a.radius) return data;
norm = b.mat.clone().mult(norm);
data.norm = norm.clone().neg();
data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius));
}
return data;
};
请注意,b.verts2
指的是真实世界坐标中多边形的顶点。
我知道 Vector class 没有问题,但由于我对转换矩阵没有太多经验,class 可能是这些问题的根源错误,尽管它的代码也几乎完全来自 Impulse Engine,因此它应该可以工作。如前所述,算法总是 returns false,即使确实发生了碰撞。我在这里做错了什么?我试着去掉早期的 returns,但只有 returns 奇怪的结果,比如带有负坐标的接触点,这显然不太正确。
编辑:修改了我的向量 class 的垂直函数,使其以与脉冲引擎相同的方式工作(两种方式都是正确的,但我认为一种是顺时针方向,另一种是逆时针方向——我也修改了我的顶点反映 counterclockwise-ness)。不幸的是,它仍然没有通过测试。
嗯,问题很多,我真的不明白你想做什么,因为它看起来过于复杂。例如为什么矩阵有trans
???为什么要使用 Y 向上屏幕作为变换的坐标系??? (修辞)
在第一个循环中。
- 首先是你在测试法向量的距离
每个顶点的,应该测试顶点位置。
- 您还使用
vec.dot
函数计算距离
returns 距离的平方。但是你测试半径,你
应该测试 if(sep2 < radius * radius)
- 而且你的比较方式错了
测试是否小于半径的平方(不大于)
- 然后当你在半径内检测到顶点时,你 return 数据
对象,但忘记将在圆圈内找到的顶点放在
data.contacts
数组。
- 我不太清楚保留索引的目的是什么
distant vect 是但是函数的其余部分对零意义
我???? :( 我试着去理解它。
您只需
检查多边形上是否有任何顶点比半径更近,如果是,那么你有截距(或完全在里面)
然后需要检查每条线段的距离
如果您不需要截距(或者如果您需要截距则低于该截距),可以对每个线段执行以下操作,只使用一个或另一个。
// circle is a point {x:?,y:?}
// radius = is the you know what
// p1,p2 are the start and end points of a line
checkLineCircle = function(circle,radius,p1,p2){
var v1 = {};
var v2 = {};
var v3 = {};
var u;
// get dist to end of line
v2.x = circle.x - p1.x;
v2.y = circle.y - p1.y;
// check if end points are inside the circle
if( Math.min(
Math.hypot(p2.x - circle.x, p2.y - circle.y),
Math.hypot(v2.x, v2.y)
) <= radius){
return true;
}
// get the line as a vector
v1.x = p2.x - p1.x;
v1.y = p2.y - p1.y;
// get the unit distance of the closest point on the line
u = (v2.x * v1.x + v2.y * v1.y)/(v1.y * v1.y + v1.x * v1.x);
// is this on the line segment
if(u >= 0 && u <= 1){
v3.x = v1.x * u; // get the point on the line segment
v3.y = v1.y * u;
// get the distance to that point and return true or false depending on the
// it being inside the circle
return (Math.hypot(v3.y - v2.y, v3.x - v2.x) <= radius);
}
return false; // no intercept
}
为每个 line.To 节省时间将圆心变换到多边形本地,而不是变换多边形上的每个点。
如果你需要拦截点然后使用下面的函数
// p1,p2 are the start and end points of a line
// returns an array empty if no points found or one or two points depending on the number of intercepts found
// If two points found the first point in the array is the point closest to the line start (p1)
function circleLineIntercept(circle,radius,p1,p2){
var v1 = {};
var v2 = {};
var ret = [];
var u1,u2,b,c,d;
// line as vector
v1.x = p2.x - p1.x;
v1.y = p2.y - p1.y;
// vector to circle center
v2.x = p1.x - circle.x;
v2.y = p1.y - circle.y;
// dot of line and circle
b = (v1.x * v2.x + v1.y * v2.y) * -2;
// length of line squared * 2
c = 2 * (v1.x * v1.x + v1.y * v1.y);
// some math to solve the two triangles made by the intercept points, the circle center and the perpendicular line to the line.
d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - radius * radius));
// will give a NaN if no solution
if(isNaN(d)){ // no intercept
return ret;
}
// get the unit distance of each intercept to the line
u1 = (b - d) / c;
u2 = (b + d) / c;
// check the intercept is on the line segment
if(u1 <= 1 && u1 >= 0){
ret.push({x:line.p1.x + v1.x * u1, y : line.p1.y + v1.y * u1 });
}
// check the intercept is on the line segment
if(u2 <= 1 && u2 >= 0){
ret.push({x:line.p1.x + v1.x * u2, y : line.p1.y + v1.y * u2});
}
return ret;
}
多边形迭代由您来完成。
我一直在尝试基于 Randy Gaul's C++ Impulse Engine 实现圆和多边形之间的碰撞检测,非常严格地遵循代码,但算法从来没有 returns 正确。
这是 JSFiddle。 (为方便起见,身体使用 HTML5 Canvas API 渲染)
一段代码(只是碰撞检测):
const circPoly = (a, b) => {
let data = {},
center = a.pos;
data.contacts = [];
center = b.mat.clone().trans().mult(center.clone().sub(b.pos));
let sep = -Number.MAX_VALUE,
faceNorm = 0;
for (let i = 0; i < b.verts2.length; ++i) {
let sep2 = b.norms[i].dot(center.clone().sub(b.verts2[i]));
if (sep2 > a.radius) return data;
if (sep2 > sep) { sep = sep2; faceNorm = i; }
}
let v1 = b.verts2[faceNorm],
v2 = b.verts2[faceNorm + 1 < b.verts2.length ? faceNorm + 1 : 0];
if (sep < 0.0001) {
data.depth = a.radius;
data.norm = b.mat.clone().mult(b.norms[faceNorm]).neg();
data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius));
return data;
}
let dot1 = center.clone().sub(v1).dot(v2.clone().sub(v1)),
dot2 = center.clone().sub(v2).dot(v1.clone().sub(v2));
data.depth = a.radius - sep;
if (dot1 <= 0) {
if (center.dist2(v1) > a.radius * a.radius) return data;
let norm = v1.clone().sub(center);
norm = b.mat.clone().mult(norm);
norm.norm();
data.norm = norm;
v1 = b.mat.clone().mult(v1.clone().add(b.pos));
data.contacts[0] = v1;
} else if (dot2 <= 0) {
if (center.dist2(v2) > a.radius * a.radius) return data;
let norm = v2.clone().sub(center);
norm = b.mat.clone().mult(norm);
norm.norm();
data.norm = norm;
v2 = b.mat.clone().mult(v2.clone().add(b.pos));
data.contacts[0] = v2;
} else {
let norm = b.norms[faceNorm];
if (center.clone().sub(v1).dot(norm) > a.radius) return data;
norm = b.mat.clone().mult(norm);
data.norm = norm.clone().neg();
data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius));
}
return data;
};
请注意,b.verts2
指的是真实世界坐标中多边形的顶点。
我知道 Vector class 没有问题,但由于我对转换矩阵没有太多经验,class 可能是这些问题的根源错误,尽管它的代码也几乎完全来自 Impulse Engine,因此它应该可以工作。如前所述,算法总是 returns false,即使确实发生了碰撞。我在这里做错了什么?我试着去掉早期的 returns,但只有 returns 奇怪的结果,比如带有负坐标的接触点,这显然不太正确。
编辑:修改了我的向量 class 的垂直函数,使其以与脉冲引擎相同的方式工作(两种方式都是正确的,但我认为一种是顺时针方向,另一种是逆时针方向——我也修改了我的顶点反映 counterclockwise-ness)。不幸的是,它仍然没有通过测试。
嗯,问题很多,我真的不明白你想做什么,因为它看起来过于复杂。例如为什么矩阵有trans
???为什么要使用 Y 向上屏幕作为变换的坐标系??? (修辞)
在第一个循环中。
- 首先是你在测试法向量的距离 每个顶点的,应该测试顶点位置。
- 您还使用
vec.dot
函数计算距离 returns 距离的平方。但是你测试半径,你 应该测试if(sep2 < radius * radius)
- 而且你的比较方式错了 测试是否小于半径的平方(不大于)
- 然后当你在半径内检测到顶点时,你 return 数据
对象,但忘记将在圆圈内找到的顶点放在
data.contacts
数组。 - 我不太清楚保留索引的目的是什么 distant vect 是但是函数的其余部分对零意义 我???? :( 我试着去理解它。
您只需
检查多边形上是否有任何顶点比半径更近,如果是,那么你有截距(或完全在里面)
然后需要检查每条线段的距离
如果您不需要截距(或者如果您需要截距则低于该截距),可以对每个线段执行以下操作,只使用一个或另一个。
// circle is a point {x:?,y:?}
// radius = is the you know what
// p1,p2 are the start and end points of a line
checkLineCircle = function(circle,radius,p1,p2){
var v1 = {};
var v2 = {};
var v3 = {};
var u;
// get dist to end of line
v2.x = circle.x - p1.x;
v2.y = circle.y - p1.y;
// check if end points are inside the circle
if( Math.min(
Math.hypot(p2.x - circle.x, p2.y - circle.y),
Math.hypot(v2.x, v2.y)
) <= radius){
return true;
}
// get the line as a vector
v1.x = p2.x - p1.x;
v1.y = p2.y - p1.y;
// get the unit distance of the closest point on the line
u = (v2.x * v1.x + v2.y * v1.y)/(v1.y * v1.y + v1.x * v1.x);
// is this on the line segment
if(u >= 0 && u <= 1){
v3.x = v1.x * u; // get the point on the line segment
v3.y = v1.y * u;
// get the distance to that point and return true or false depending on the
// it being inside the circle
return (Math.hypot(v3.y - v2.y, v3.x - v2.x) <= radius);
}
return false; // no intercept
}
为每个 line.To 节省时间将圆心变换到多边形本地,而不是变换多边形上的每个点。
如果你需要拦截点然后使用下面的函数
// p1,p2 are the start and end points of a line
// returns an array empty if no points found or one or two points depending on the number of intercepts found
// If two points found the first point in the array is the point closest to the line start (p1)
function circleLineIntercept(circle,radius,p1,p2){
var v1 = {};
var v2 = {};
var ret = [];
var u1,u2,b,c,d;
// line as vector
v1.x = p2.x - p1.x;
v1.y = p2.y - p1.y;
// vector to circle center
v2.x = p1.x - circle.x;
v2.y = p1.y - circle.y;
// dot of line and circle
b = (v1.x * v2.x + v1.y * v2.y) * -2;
// length of line squared * 2
c = 2 * (v1.x * v1.x + v1.y * v1.y);
// some math to solve the two triangles made by the intercept points, the circle center and the perpendicular line to the line.
d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - radius * radius));
// will give a NaN if no solution
if(isNaN(d)){ // no intercept
return ret;
}
// get the unit distance of each intercept to the line
u1 = (b - d) / c;
u2 = (b + d) / c;
// check the intercept is on the line segment
if(u1 <= 1 && u1 >= 0){
ret.push({x:line.p1.x + v1.x * u1, y : line.p1.y + v1.y * u1 });
}
// check the intercept is on the line segment
if(u2 <= 1 && u2 >= 0){
ret.push({x:line.p1.x + v1.x * u2, y : line.p1.y + v1.y * u2});
}
return ret;
}
多边形迭代由您来完成。