【发布时间】:2022-09-25 06:13:04
【问题描述】:
标签: java geometry processing collision
标签: java geometry processing collision
我必须承认我只了解 MBo 答案的一小部分(听起来很可靠(+1))。
这是我对他的公式的理解:
- adding x0 and y0 (the first ellipse centre) moves the origin (x0 + ... , y0 + ...)
- a and b are the two radii of the ellipse (e.g. width / 2, height / 2)
- t is the angle between the ellipse and the cirle on the right (I think)
- using the polar to cartesian coordinate conversion formula(*) we get two "sides" of a triangle where the diagonal is the distance to the circle
- x = cos(angle) * width/2, y = sin(angle) * height/2, to which add the ellipse offset (x0, y0)
- x^2 + y^2 = distance^2 (Pythagoras theorem)
(x0+a*cos(t))^2+(y0+b*sin(t))^2 = D^2
cartesian coordinate conversion formula(*)
我没有得到的是衍生/微分部分(因为我在学校没有注意,我还没有回去正确地学习这些,但是)
这是一个可视化上述内容的基本草图,并使用我们可以使用atan2(y, x)(其中 y = 圆 y - 椭圆 y 和 x = 圆 x - 椭圆 y)来计算椭圆之间的角度这一事实。然后,使用极坐标转换,我们可以计算椭圆上离圆最近的点。如果这个最近点和圆心之间的距离小于圆的半径,那么它们必须相交。
// e1 - ellipse 1 with 2:1 width to height ratio
float e1x = 200;
float e1y = 200;
float e1w = 200;
float e1h = 100;
// e1 - ellipse 2 with 1:1 widht to height ratio (circle)
float e2x = 400;
float e2y = 200;
float e2w = 100;
float e2h = 100;
void setup(){
size(600, 400);
stroke(128);
strokeWeight(3);
}
void draw(){
background(32);
fill(255);
ellipse(e2x, e2y, e2w, e2h);
boolean areE1E2Intersecting = isColliding();
fill(areE1E2Intersecting ? color(0, 192, 0) : color(255));
ellipse(e1x, e1y, e1w, e1h);
}
boolean isColliding(){
boolean result = false;
// calculate angle between e1 and e2
float angleFromE1ToE2 = atan2(e2y - e1y, e2x - e1x);
// calculate the point one e1 towards e2 (
// 0.5 is beacause we want radius not diameter (w,h)
float xE1ToE2 = e1x + (cos(angleFromE1ToE2) * e1w * 0.5);
float yE1ToE2 = e1y + (sin(angleFromE1ToE2) * e1h * 0.5);
// optional: visualise the point
fill(255, 64);
stroke(255);
triangle(xE1ToE2, yE1ToE2,
xE1ToE2, e2y,
e2x, e2y);
ellipse(xE1ToE2, yE1ToE2, 15, 15);
fill(255);
stroke(128);
// if the distance between the closest point on the ellipse towards the circle
// is smaller than the circle's radius then they're colliding
result = dist(xE1ToE2, yE1ToE2, e2x, e2y) < e2w * 0.5;
return result;
}
void mouseDragged(){
e1x = mouseX;
e1y = mouseY;
}
拖动鼠标移动椭圆。
代码有点冗长,但评论说:希望很容易理解。 可以根据需要对其进行重构以进行重用(例如,删除可视化、更改函数以使其接受参数而不是全局变量等)
更新
正如 Mbo 指出的那样,t 不是角度。
使用我上面的方法,您可以对从 ellipse-circle 到 ellipse-ellipse 进行最小的调整。 (您的问题将两个元素都称为圆圈,尽管图像显示了一个椭圆和一个圆圈,因此我在上面的 sn-p。您的评论澄清了您是在椭圆到椭圆的交点之后)
您可以对我的椭圆到椭圆交点的方法进行细微调整。请注意,这是一个粗略的近似值/不完美。 注意标记为最接近对面椭圆的点。它们与中心之间的线不一致。 (我怀疑这是因为我在极坐标到笛卡尔转换中使用了半径的一半宽度/高度,这有点偏离(特别是在 90 度增量之间的角度)
// ellipse 1
float e1x = 200;
float e1y = 200;
float e1w = 200;
float e1h = 100;
// ellipse 2
float e2x = 400;
float e2y = 200;
float e2w = 200;
float e2h = 300;
void setup(){
size(600, 400);
stroke(128);
strokeWeight(3);
noFill();
}
void draw(){
background(32);
stroke(255);
ellipse(e2x, e2y, e2w, e2h);
boolean areE1E2Intersecting = isColliding();
stroke(areE1E2Intersecting ? color(0, 192, 0) : color(255));
ellipse(e1x, e1y, e1w, e1h);
}
boolean isColliding(){
boolean result = false;
// calculate angle between e1 and e2
float angleFromE1ToE2 = atan2(e2y - e1y, e2x - e1x);
// calculate the point one e1 towards e2
// 0.5 is beacause we want radius not diameter (w,h)
float xE1ToE2 = e1x + (cos(angleFromE1ToE2) * e1w * 0.5);
float yE1ToE2 = e1y + (sin(angleFromE1ToE2) * e1h * 0.5);
float radiusFromE1ToE2 = dist(e1x, e1y, xE1ToE2, yE1ToE2);
float angleFromE2ToE1 = PI + angleFromE1ToE2;
float xE2ToE1 = e2x + (cos(angleFromE2ToE1) * e2w * 0.5);
float yE2ToE1 = e2y + (sin(angleFromE2ToE1) * e2h * 0.5);
float radiusFromE2ToE1 = dist(e2x, e2y, xE2ToE1, yE2ToE1);
result = dist(e1x, e1y, e2x, e2y) < (radiusFromE1ToE2 + radiusFromE2ToE1);
// optional: visual debugging
ellipse(xE1ToE2, yE1ToE2, 15, 15);
ellipse(xE2ToE1, yE2ToE1, 15, 15);
line(e1x, e1y, e2x, e2y);
return result;
}
void mouseDragged(){
e1x = mouseX;
e1y = mouseY;
}
请注意,上述内容没有考虑到非常不同或不同的椭圆方向的纵横比(顺便说一句,您的问题根本没有提到这一点)。
快速搜索我看到math 涉及移动,但也有一些有趣的近似值,例如Olli's。
可能还有其他解决方案,我希望在处理中看到更多选项。我能想到的一种蛮力/hacky 解决方法是使用blendMode(DIFFERENCE)(将突出显示形状之间的交叉点)然后使用loadPixels(); 和pixels[] 搜索交叉点颜色的第一个像素。如果您需要优化速度(特别是对于高分辨率草图),您可以渲染主草图的较小屏幕外缓冲区(通过createGraphics())。 (如果需要,这将允许您使用主草图图形中的混合模式和不同颜色(否则调用get() 将返回草图的PImage“快照”,您可以根据需要resize()))
这是一个基本的草图来说明这个想法:
// e1 - ellipse 1 with 2:1 width to height ratio
float e1x = 200;
float e1y = 200;
float e1w = 200;
float e1h = 100;
// e1 - ellipse 2 with 1:1 widht to height ratio (circle)
float e2x = 400;
float e2y = 200;
float e2w = 200;
float e2h = 300;
void setup(){
size(600, 400);
noStroke();
blendMode(DIFFERENCE);
}
void draw(){
background(0);
fill(255, 0, 0);
ellipse(e1x, e1y, e1w, e1h);
fill(0, 255, 0);
ellipse(e2x, e2y, e2w, e2h);
fill(255);
text("is colliding: " + isColliding(), 10, 15);
}
boolean isColliding(){
boolean result = false;
loadPixels();
int numPixels = pixels.length;
for(int i = 0 ; i < numPixels; i++){
// because the ellipse colours are red and green, difference is yellow
// so that's what we search for
if(pixels[i] == color(255, 255, 0)){
return true;
}
}
return result;
}
void mouseDragged(){
e1x = mouseX;
e1y = mouseY;
}
【讨论】:
t 不是角度,它是特殊参数(接近角度,但不等于)。所以我可以假设这些计算不是很精确 - 尽管对于低偏心率(类似圆)椭圆的误差相当小,并且方法可能适用于实践目的。
t/differentiation/derrivative 部分,我们将不胜感激。 (虽然我会尊重你的时间,否则)。
移动坐标系以圆心为原点。现在让椭圆居中是x0, y0。写出从原点到椭圆的平方距离
(x0+a*cos(t))^2+(y0+b*sin(t))^2 = D^2
并找到最小值:通过t 区分,使导数=0,求解未知t,得到最接近原点的点(似乎应该求解四次方程)
如果距离小于圆半径,则确实存在相交。
更新。它应该如何工作:
从原点到椭圆的距离是到所有椭圆点的最小距离。由数学可知。分析函数F(t)的最小值点是导数F'(t)==0(当函数达到最小值或最大值时,导数改变它的符号)。所以我们可以得到函数导数方程,得到它的零点并找到函数有最小值的点(我们还必须检查它不是最大值,并且二阶导数不为零F''(t) != 0)。距离函数对于这些目的来说太复杂了(sqrt 导致长导数表达式),但幸运的是,平方距离与距离具有相同的极值,所以我们可以将 x^2+y^2 写成椭圆的点,通过一些方便的方式参数化,得到导数,找到最低限度。
轴对齐椭圆,半轴 a 和 b 和中心 x0,y0 hase 方程
x = x0+a*cos(t)
y = y0+b*sin(t)
上面给出了平方距离公式。它是导数(通过变量t)
d(D2)/dt = -2*a^2*cos(t)*sin(t)+2*b^2*cos(t)*sin(t)-x0*a*sin(t)+y0*b*cos(t) = 0
为了求解这个方程,我们可以用half-angle tangent formulas 替换cos 和sin,结果将是未知u=tan(t/2) 的四次(t 次)多项式。我不想在这里制作这些公式,因为它们很长,而且我不确定它们是否易于使用。也许有一些库实现点椭圆距离。顺便说一句,我发现here similar approach is described with code,也可以看看其他答案——看起来数值方法(like this one)实现起来要简单得多。
【讨论】: