簡體   English   中英

在java中找到兩個圓之間距離的最有效方法?

[英]Most efficient way to find distance between two circles in java?

所以顯然計算平方根不是很有效,這讓我想知道找出兩個圓之間的距離(我在下面稱之為范圍)的最佳方法是什么?

找到兩個圓之間的范圍

所以通常我會解決:

a^2 + b^2 = c^2
dy^2 + dx^2 = h^2
dy^2 + dx^2 = (r1 + r2 + range)^2
(dy^2 + dx^2)^0.5 = r1 + r2 + range
range = (dy^2 + dx^2)^0.5 - r1 - r2

當您只是尋找碰撞“范圍”為 0 的情況時,嘗試避免平方根效果很好:

 if ( (r1 + r2 + 0 )^2 > (dy^2 + dx^2) )

但是,如果我試圖計算出那個距離,我最終會得到一些笨拙的等式,例如:

 range(range + 2r1 + 2r2) = dy^2 + dx^2 - (r1^2 + r2^2 + 2r1r2)

這不會去任何地方。 至少我不知道如何解決這里的范圍......

顯而易見的答案是三角學並首先找到 theta:

  Tan(theta) = dy/dx
  theta = dy/dx * Tan^-1

然后找到hypotemuse Sin(theta) = dy/hh = dy/Sin(theta)

最后算出范圍 range + r1 + r2 = dy/Sin(theta) range = dy/Sin(theta) - r1 - r2

所以這就是我所做的,並得到了一個看起來像這樣的方法:

private int findRangeToTarget(ShipEntity ship, CircularEntity target){


    //get the relevant locations
    double shipX = ship.getX();
    double shipY = ship.getY();
    double targetX =  target.getX();
    double targetY =  target.getY();
    int shipRadius = ship.getRadius();
    int targetRadius = target.getRadius();

    //get the difference in locations:
    double dX = shipX - targetX;
    double dY = shipY - targetY;

    // find angle 
    double theta = Math.atan(  ( dY / dX ) );

    // find length of line ship centre - target centre
    double hypotemuse = dY / Math.sin(theta);

    // finally range between ship/target is:
    int range = (int) (hypotemuse - shipRadius - targetRadius);

    return range;

}

所以我的問題是,使用 tan 和 sin 比找到平方根更有效嗎?

我也許能夠重構我的一些代碼以從另一種方法(我必須解決它)獲取 theta 值,這值得嗎?

還是完全有另一種方式?

如果我問的是顯而易見的問題,或者犯了任何基本錯誤,請原諒我,我已經很久沒有使用高中數學來做任何事情了......

歡迎任何提示或建議!

****編輯****

具體來說,我正在嘗試在游戲中創建一個“掃描儀”設備,用於檢測敵人/障礙物何時接近/消失等。掃描儀將通過音頻或圖形條或其他方式傳遞此信息。 因此,雖然我不需要確切的數字,但理想情況下我想知道:

  1. 目標比以前更近/更遠
  2. 目標 A 比目標 B、C、D 更近/更遠...
  3. 一個(希望是線性的?)比率,表示相對於 0(碰撞)和最大范圍(一些常數),目標離船有多遠
  4. 有些目標會非常大(行星?)所以我需要考慮半徑

我希望有一些巧妙的優化/近似可能(dx + dy +(dx,dy 中更長的那個?),但是對於所有這些要求,也許不是......

Math.hypot旨在獲得更快、更准確的sqrt(x^2 + y^2)形式的計算。 所以這應該只是

return Math.hypot(x1 - x2, y1 - y2) - r1 - r2;

我無法想象任何比這更簡單,也更快的代碼。

如果你真的需要精確的距離,那么你就無法真正避免平方根。 三角函數至少和平方根計算一樣糟糕,甚至更糟。

但是如果您只需要近似距離,或者如果您只需要各種圓組合的相對距離,那么您肯定可以做一些事情。 例如,如果您只需要相對距離,請注意平方數與其平方根具有相同的大於關系。 如果你只是比較不同的對,跳過平方根步驟,你會得到相同的答案。

如果您只需要近似距離,那么您可能會認為h大致等於較長的相鄰邊。 這種近似值的偏差永遠不會超過兩倍。 或者您可以使用三角函數的查找表——這比任意平方根的查找表更實用。

我厭倦了首先確定使用 tan, sine 時的答案是否與使用 sqrt 函數時相同。

public static void main(String[] args) throws Exception {
    // TODO Auto-generated method stub
     double shipX = 5;
        double shipY = 5;
        double targetX =  1;
        double targetY =  1;
        int shipRadius = 2;
        int targetRadius = 1;

        //get the difference in locations:
        double dX = shipX - targetX;
        double dY = shipY - targetY;

        // find angle 
        double theta = Math.toDegrees(Math.atan(  ( dY / dX ) ));

        // find length of line ship centre - target centre
        double hypotemuse = dY / Math.sin(theta);
        System.out.println(hypotemuse);
        // finally range between ship/target is:
        float range = (float) (hypotemuse - shipRadius - targetRadius);
        System.out.println(range);

        hypotemuse = Math.sqrt(Math.pow(dX,2) + Math.pow(dY,2));
        System.out.println(hypotemuse);
        range = (float) (hypotemuse - shipRadius - targetRadius);
        System.out.println(range);
}

我得到的答案是:4.700885452542996

1.7008854

5.656854249492381

2.6568542

現在似乎與 sqrt 值之間的差異更正確。

  1. 談論性能:考慮您的代碼片段:

我計算了表演時間——結果如下:

    public static void main(String[] args) throws Exception {
    // TODO Auto-generated method stub
    long lStartTime = new Date().getTime(); //start time
    double shipX = 555;
        double shipY = 555;
        double targetX =  11;
        double targetY =  11;
        int shipRadius = 26;
        int targetRadius = 3;

        //get the difference in locations:
        double dX = shipX - targetX;
        double dY = shipY - targetY;

        // find angle 
        double theta = Math.toDegrees(Math.atan(  ( dY / dX ) ));

        // find length of line ship centre - target centre
        double hypotemuse = dY / Math.sin(theta);
        System.out.println(hypotemuse);
        // finally range between ship/target is:
        float range = (float) (hypotemuse - shipRadius - targetRadius);
        System.out.println(range);


        long lEndTime = new Date().getTime(); //end time

          long difference = lEndTime - lStartTime; //check different

          System.out.println("Elapsed milliseconds: " + difference);
}

答案 - 639.3204215458475、610.32043,經過的毫秒數:2

當我們嘗試使用 sqrt root 時:

public static void main(String[] args) throws Exception {
    // TODO Auto-generated method stub
    long lStartTime = new Date().getTime(); //start time
    double shipX = 555;
        double shipY = 555;
        double targetX =  11;
        double targetY =  11;
        int shipRadius = 26;
        int targetRadius = 3;

        //get the difference in locations:
        double dX = shipX - targetX;
        double dY = shipY - targetY;

        // find angle 
        double theta = Math.toDegrees(Math.atan(  ( dY / dX ) ));

        // find length of line ship centre - target centre

       double hypotemuse = Math.sqrt(Math.pow(dX,2) + Math.pow(dY,2));
        System.out.println(hypotemuse);
        float range = (float) (hypotemuse - shipRadius - targetRadius);
        System.out.println(range);

        long lEndTime = new Date().getTime(); //end time

          long difference = lEndTime - lStartTime; //check different

          System.out.println("Elapsed milliseconds: " + difference);
}

答案 - 769.3321779309637、740.33215,經過的毫秒數:1

現在,如果我們檢查差異,兩個答案之間的差異也很大。

因此我會說,如果你讓游戲更准確,數據會更有趣,這對用戶來說應該是有趣的。

在“硬”幾何軟件中, sqrt通常帶來的問題不是它的性能,而是隨之而來的精度損失。 在您的情況下, sqrt適合該法案。

如果您發現sqrt確實帶來了性能損失 - 您知道,僅在需要時進行優化 - 您可以嘗試使用線性近似。

f(x) ~ f(X0) + f'(x0) * (x - x0)
sqrt(x) ~ sqrt(x0) + 1/(2*sqrt(x0)) * (x - x0)

因此,您計算sqrt的查找表 (LUT),並在給定x使用最近的x0 當然,當您應該回退到常規計算時,這會限制您的可能范圍。 現在,一些代碼。

class MyMath{
    private static double[] lut;
    private static final LUT_SIZE = 101;
    static {
        lut = new double[LUT_SIZE];
        for (int i=0; i < LUT_SIZE; i++){
            lut[i] = Math.sqrt(i);
        }
    }
    public static double sqrt(final double x){
        int i = Math.round(x);
        if (i < 0)
            throw new ArithmeticException("Invalid argument for sqrt: x < 0");
        else if (i >= LUT_SIZE)
            return Math.sqrt(x);
        else
            return lut[i] + 1.0/(2*lut[i]) * (x - i);
    }
}

(我沒有測試這段代碼,請原諒並糾正任何錯誤)

此外,在寫完這一切之后,可能已經有一些近似的、有效的、替代的數學庫了。 你應該尋找它,但只有當你發現性能確有必要。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM