【问题标题】:Most efficient way to find distance between two circles in java?在java中查找两个圆之间距离的最有效方法?
【发布时间】:2012-05-19 14:38:02
【问题描述】:

显然计算平方根不是很有效,这让我想知道找出两个圆之间的距离(我在下面称为范围)的最佳方法是什么?

所以通常我会锻炼:

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

然后找到下位点 Sin(theta) = dy/h h = dy/Sin(θ)

最后算出范围 范围 + r1 + r2 = dy/Sin(theta) 范围 = 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 的更长?),但考虑到所有这些要求,可能不会......

【问题讨论】:

  • 我被你的斜边逗乐了!
  • 不要使用toDegreesMath.sin 使用弧度(就像数学中的所有三角函数一样)
  • 您是否使用sqrt 分析过您的代码?
  • @BrunoKim :D 这也不是我第一次这样拼写...我想我更喜欢我的版本,灵感来自我最喜欢的动物,hypotomus,我的意思是河马... hippyhoppytoppamossymus...嗯。我可能有轻度阅读障碍。
  • @bizso09 我不确定'profiled'是什么意思,但我猜'test for speed'?如果是这样,下面的 Ankit 似乎已经做到了。我想在确定性能存在问题之前,我会遵循不要过分担心的建议。

标签: java performance math trigonometry


【解决方案1】:

Math.hypot 旨在获得更快、更准确的sqrt(x^2 + y^2) 形式的计算。所以这应该只是

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

我无法想象任何代码会比这更简单,也不会更快。

【讨论】:

  • 从其他不要太担心的答案中获取线索,我认为这似乎是目前最明智的解决方案。如果将来性能成为问题,我还将更多地研究近似值并查找表格。
  • 真的更快吗?你怎么知道的?
  • 这基本上是明确的你应该怎么做。如果JDK为你提供了一个内置的方法来计算像这样的三角形的斜边,那就是用本机代码实现,我觉得很舒服,假设这就是你打算这样做的方式。这是投反对票的原因吗?
  • Math.hypot 有额外的逻辑来处理下溢和下溢,这使得它比原始斜边计算慢。如果您不打算超出中间值的两倍范围(即您的源数字小于大约 2^511),则 Math.hypot 不一定比自己做的快,而是可能会慢一些。方法本身的描述是这样说的:“返回 sqrt(x2 +y2) 没有中间溢出或下溢。”
  • Math.hypot 慢很多 link
【解决方案2】:

如果你真的需要准确的距离,那么你就无法真正避免平方根。三角函数至少和平方根计算一样糟糕,如果不是更糟的话。

但是,如果您只需要 近似 距离,或者如果您只需要 相对 各种圆组合的距离,那么您肯定可以做一些事情。例如,如果您只需要相对距离,请注意平方数与其平方根具有相同的大于关系。如果您只是比较不同的配对,请跳过平方根步骤,您会得到相同的答案。

如果您只需要近似距离,那么您可能会认为h 大致等于较长的相邻边。这个近似值的偏差永远不会超过两倍。或者,您可以为三角函数使用查找表——这比查找任意平方根的查找表更实用。

【讨论】:

  • 谢谢欧内斯特,那里有一些有用的指示。我想知道是否像添加 dx + dy (减去半径)和两侧中的较长者这样的东西可能会给出某种体面的近似值来满足我的大部分条件(我只是将它们添加到我的帖子中)。我认为最大的问题是我希望将这些数据的平滑表示输出给用户......无论如何,如果我在第二天左右不能想出更好的东西,我会将此答案标记为正确.
【解决方案3】:

我厌倦了首先弄清楚我们使用 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. 谈论性能: 考虑您的代码 sn-p :

我计算了表演时间——结果如下:

    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 one 时:

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

现在,如果我们检查差异,两个答案之间的差异也很大。

因此我会说,如果你让游戏更准确,那么数据对用户来说会更有趣。

【讨论】:

  • 感谢您的测试,很高兴看到答案的差异。与精确相比,准确性对我来说并不重要。 (我认为我得到了正确的方法)无论如何我都会记住它以供将来参考。
【解决方案4】:

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);
    }
}

(我没有测试此代码,请原谅并纠正任何错误)

另外,在写完这一切之后,可能已经有一些近似的、高效的、替代的数学库了。您应该寻找它,但只有在您发现确实需要性能时

【讨论】:

  • 嗯,如果x &lt; 0Math.round(x) &gt;= 0,它将提供答案。
  • 我想我会听从你的建议,暂时不用太担心优化问题。我想最终将我的游戏移植到手机上,所以我想那时我必须进行优化。到那时,线性近似和查表可能就是答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-03
  • 1970-01-01
  • 2012-09-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多