【问题标题】:Scaled circle to circle collision缩放圆到圆碰撞
【发布时间】:2022-09-25 06:13:04
【问题描述】:

如何制作一个缩放的圆圈到圆圈的碰撞? 例如: example

我有 2 个圆圈,一个圆圈 X 和 Y 大小相同,另一个在 x 大小上更大。

如何检测两个圆之间的碰撞?

    标签: java geometry processing collision


    【解决方案1】:

    我必须承认我只了解 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 不是角度,它是特殊参数(接近角度,但不等于)。所以我可以假设这些计算不是很精确 - 尽管对于低偏心率(类似圆)椭圆的误差相当小,并且方法可能适用于实践目的。
    • 虽然一开始很有希望,但如果你让第二个圆圈也有不同的大小(我用高度做了这个),碰撞就像它不是不同的大小一样
    • @Mbo 谢谢你的解释。如果您能抽出一点时间在您的答案中进一步解释t/differentiation/derrivative 部分,我们将不胜感激。 (虽然我会尊重你的时间,否则)。
    • @5x9x7x2x7x9 我发布了一个更新,其中包括指向任意大小(和方向)椭圆的现有解决方案的链接。您的问题提到了圆圈(即使您的意思是省略号),没有包含很多细节,甚至没有代码 sn-p 显示您解决问题的尝试。我最初的解决方案,因为代码中的 cmets 解释显示椭圆到圆的选项(不是椭圆到椭圆)。您提供的信息越好,其他人就越容易提供更好的答案(并节省时间)。希望上面有一个对您有用的解决方案。
    • @George Profenza 干得好!我为微分直觉添加了一些描述。
    【解决方案2】:

    移动坐标系以圆心为原点。现在让椭圆居中是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 写成椭圆的点,通过一些方便的方式参数化,得到导数,找到最低限度。

    轴对齐椭圆,半轴 ab 和中心 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 替换cossin,结果将是未知u=tan(t/2) 的四次(t 次)多项式。我不想在这里制作这些公式,因为它们很长,而且我不确定它们是否易于使用。也许有一些库实现点椭圆距离。顺便说一句,我发现here similar approach is described with code,也可以看看其他答案——看起来数值方法(like this one)实现起来要简单得多。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-20
      • 1970-01-01
      • 1970-01-01
      • 2012-01-15
      • 1970-01-01
      相关资源
      最近更新 更多