检测和组合重叠/碰撞圆的算法

时间:2014-11-13 17:39:34

标签: java algorithm 2d collision-detection

我正在尝试编写一种时间有效的算法,该算法可以检测一组重叠的圆圈,并在表示该组的组的“中间”创建一个圆圈。这个实际应用是在地图上表示GPS位置,将转换放入笛卡尔坐标已经处理,这是不相关的,期望的效果是在不同的缩放级别上,靠近在一起的点的簇只显示为单个圆圈(将在最终版本的中心打印点数)

在此示例中,圆的半径为15,因此距离计算(毕达哥拉斯)不是平方根,并且与碰撞检测的225相比较。我正在努力减少时间,但问题是这真的需要非常快速地发生,因为它是一个面向需要快速和好看的代码的用户。

我已经给了它一个去,我很适合使用小数据集。 2个大问题,它需要太长时间,如果所有点都在彼此之上,它可能会耗尽内存。

我采取的路线是计算第一次通过中每个点之间的距离,然后先取最短距离并从那里开始合并,任何已合并的东西都不符合该通行证的合并条件,整个列表再次传回到距离计算,直到没有任何变化。

老实说,我认为它需要一种彻底的转变,我认为这有点超出我的意义。为了便于发布,我将代码重新分配到一个类中,并生成随机点以举例说明。

package mergepoints;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Merger {

    public static void main(String[] args) {

        Merger m = new Merger();

        m.subProcess(m.createRandomList());

    }

    private List<Plottable> createRandomList() {
        List<Plottable> points = new ArrayList<>();
        for (int i = 0; i < 50000; i++) {
            Plottable p = new Plottable();
            p.location = new Point((int) Math.floor(Math.random() * 1000),
                    (int) Math.floor(Math.random() * 1000));
            points.add(p);
        }
        return points;

    }

    private List<Plottable> subProcess(List<Plottable> visible) {

        List<PlottableTuple> tuples = new ArrayList<PlottableTuple>();

        // create a tuple to store distance and matching objects together,
        for (Plottable p : visible) {
            PlottableTuple tuple = new PlottableTuple();
            tuple.a = p;
            tuples.add(tuple);
        }

        // work out each Plottable relative distance from
        // one another and order them by shortest first.
        // We may need to do this multiple times for one set so going in own
        // method.

        // this is the bit that takes ages

        setDistances(tuples);

        // Sort so that smallest distances are at the top.
        // parse the set and combine any pair less than the smallest distance in
        // to a combined pin.
        // any plottable thats been combine is no longer eligable for combining
        // so ignore on this parse.

        List<PlottableTuple> sorted = new ArrayList<>(tuples);
        Collections.sort(sorted);

        Set<Plottable> done = new HashSet<>();
        Set<Plottable> mergedSet = new HashSet<>();
        for (PlottableTuple pt : sorted) {
            if (!done.contains(pt.a) && pt.distance <= 225) {
                Plottable merged = combine(pt, done);
                done.add(pt.a);
                for (PlottableTuple tup : pt.others) {
                    done.add(tup.a);
                }

                mergedSet.add(merged);
            }
        }

        // if we haven't processed anything we are done just return visible
        // list.
        if (done.size() == 0) {
            return visible;
        } else {
            // change the list to represent the new combined plottables and
            // repeat the process.
            visible.removeAll(done);
            visible.addAll(mergedSet);
            return subProcess(visible);
        }
    }

    private Plottable combine(PlottableTuple pt, Set<Plottable> done) {
        List<Plottable> plottables = new ArrayList<>();
        plottables.addAll(pt.a.containingPlottables);
        for (PlottableTuple otherTuple : pt.others) {
            if (!done.contains(otherTuple.a)) {
                plottables.addAll(otherTuple.a.containingPlottables);
            }
        }

        int x = 0;
        int y = 0;
        for (Plottable p : plottables) {
            Point position = p.location;
            x += position.x;
            y += position.y;
        }
        x = x / plottables.size();
        y = y / plottables.size();

        Plottable merged = new Plottable();
        merged.containingPlottables.addAll(plottables);
        merged.location = new Point(x, y);

        return merged;
    }

    private void setDistances(List<PlottableTuple> tuples) {

        System.out.println("pins: " + tuples.size());
        int loops = 0;
        // Start from the first item and loop through, then repeat but starting
        // with the next item.
        for (int startIndex = 0; startIndex < tuples.size() - 1; startIndex++) {
            // Get the data for the start Plottable
            PlottableTuple startTuple = tuples.get(startIndex);
            Point startLocation = startTuple.a.location;

            for (int i = startIndex + 1; i < tuples.size(); i++) {
                loops++;

                PlottableTuple compareTuple = tuples.get(i);

                double distance = distance(startLocation, compareTuple.a.location);

                setDistance(startTuple, compareTuple, distance);
                setDistance(compareTuple, startTuple, distance);

            }
        }
        System.out.println("loops " + loops);
    }

    private void setDistance(PlottableTuple from, PlottableTuple to,
            double distance) {
        if (distance < from.distance || from.others == null) {
            from.distance = distance;
            from.others = new HashSet<>();
            from.others.add(to);
        } else if (distance == from.distance) {
            from.others.add(to);
        }
    }

    private double distance(Point a, Point b) {
        if (a.equals(b)) {
            return 0.0;
        }
        double result = (((double) a.x - (double) b.x) * ((double) a.x - (double) b.x))
                + (((double) a.y - (double) b.y) * ((double) a.y - (double) b.y));
        return result;
    }

    class PlottableTuple implements Comparable<PlottableTuple> {
        public Plottable a;
        public Set<PlottableTuple> others;

        public double distance;

        @Override
        public int compareTo(PlottableTuple other) {
            return (new Double(distance)).compareTo(other.distance);
        }
    }

    class Plottable {
        public Point location;
        private Set<Plottable> containingPlottables;

        public Plottable(Set<Plottable> plots) {
            this.containingPlottables = plots;
        }

        public Plottable() {
            this.containingPlottables = new HashSet<>();
            this.containingPlottables.add(this);
        }

        public Set<Plottable> getContainingPlottables() {
            return containingPlottables;
        }

    }

}

3 个答案:

答案 0 :(得分:3)

首先在2D网格上映射所有圆圈。然后,您只需要将单元格中的圆圈与该单元格中的其他圆圈以及9个邻居中的圆圈进行比较(您可以使用砖块图案而不是常规网格将其减少到5个。)

如果您只需要非常近似,那么您可以将所有落入细胞的圆圈组合在一起。您可能还希望将仅具有少量圆圈的单元格与邻居合并,但这将很快。

答案 1 :(得分:0)

无论你怎么做,这个问题都需要花费大量的计算,问题是:你能预先做好所有的计算,以便在运行时它只是看一看 - 最多?我将构建一个树状结构,其中每个层都是需要为给定缩放级别绘制的所有点。它预先需要更多的计算,但在运行时你只需要快速绘制一个点列表。

我的想法是决定每个缩放级别的分辨率是什么(即缩放级别1点,接近15合并;缩放级别2点,接近30合并),然后通过你的点制作点组在相互之间的15个之内并选择一个点来表示更高变焦的组。现在你有了一个2层树。然后,您将遍历第二层,将所有彼此在30之内的点分组,依此类推,一直到最高缩放级别。现在将此树结构保存到文件中,在运行时,只需在适当的树级别绘制所有点,即可快速更改缩放级别。如果您需要添加或删除点,可以通过确定将它们附加到树的位置来动态完成。

这种方法有两个缺点:1)计算树需要很长时间,但你只需要做一次,2)你必须仔细思考关于如何构建树,基于您希望如何在更高级别完成分组。例如,在下面的图像中,顶级可能不是您想要的正确分组。也许建立基于前一层的树,你总是想回到原始点。也就是说,当您尝试权衡更快的运行时间时,总会发生一些精度损失。

enter image description here

修改

所以你有一个需要O(n ^ 2)比较的问题,你说它必须实时完成,不能预先计算,必须快速。祝你好运。

让我们稍微分析一下问题;如果你没有进行预先计算,那么为了决定哪些点可以合并,你必须比较每对点,即O(n ^ 2)比较。我建议事先建立一个树,O(n ^ 2 log n)一次,但是运行时只是一个查找,O(1)。你也可以在之前做过某些工作的地方和运行时的某些工作之间做一些事情,但是这些问题总是如此,你必须做一定数量的计算,你可以通过做一些游戏来玩游戏。它早些时候,但在一天结束时你仍然需要进行计算。

例如,如果您愿意进行某些预计算,则可以尝试保留点列表的两个副本,一个按x值排序,另一个按y排序-value,然后您可以进行4次二进制搜索,而不是比较每对点,以查找当前点的30个单位框内的所有点。对于少量的点(比如&lt; 100),更复杂的会更慢,但会将总体复杂度降低到O(n log n),使得对于大量数据更快。

编辑2

如果您担心同一地点有多个点,那么为什么不先删除冗余点,然后您就会有一个较小的搜索列表&#34;&#34; #34;

list searchList = new list()

for pt1 in points :
    boolean clean = true
    for pt2 in searchList :
        if distance(pt1, pt2) < epsilon :
            clean = false
            break
    if clean :
        searchList.add(pt1)

// Now you have a smaller list to act on with only 1 point per cluster
// ... I guess this is actually the same as my first suggestion if you make one of these search lists per zoom level. huh.    

编辑3:图表遍历

一种全新的方法是从点构建图形并对它们进行某种最长边优先的图遍历。因此,选择一个点,绘制它,并遍历其最长的边缘,绘制该点等。重复此操作,直到达到一个没有任何未遍历边缘长于缩放分辨率的点。每点的边数可以让您轻松地权衡速度以确保正确性。如果每个点的边数很小并且是常数,比如说4,那么你可以在O(n)时间内构建图形,并在O(n)时间内遍历它以绘制点。足够快,无需预先计算即可实现。

enter image description here

答案 2 :(得分:0)

在阅读其他人的回复时,我发现了一个疯狂的猜测。

进行多步比较。假设您在当前缩放级别的组合距离是20米。首先,减去(X1-X2)。如果这大于20米那么你就完成了,分数太远了。接下来,减去(Y1 - Y2)并做同样的事情以拒绝组合点。

如果您只使用水平/垂直距离作为组合的指标,那么您可以在这里停下来并感到高兴。更少的数学(没有平方或平方根)。毕达哥拉斯不会感到高兴,但你的用户可能会这样。

如果您真的坚持确切的答案,请执行上面的两个减法/比较步骤。如果这些点在水平和垂直范围内,那么你就可以用平方根进行完整的毕达哥拉斯检查。

假设所有的点都不是非常接近组合限制的高度聚类,这应该可以节省一些CPU周期。

这仍然是近似O(n ^ 2)技术,但数学应该更简单。如果你有内存,你可以存储每组点之间的距离,然后你再也不必计算它。这可能会占用比你更多的内存,并且还会以大约O(n ^ 2)的速度增长,所以要小心。

此外,您可以制作链接列表或所有点的排序数组,按照增加X或增加Y的顺序排序。(我认为您不需要两者,只需一个)。然后按排序顺序浏览列表。对于每个点,检查邻居,直到(X1 - X2)大于您的组合距离。然后停下来您不必比较O(N ^ 2)的每组点,您只需要比较一维中接近的邻居来快速将大型列表修剪为小型。当您在列表中移动时,您只需要比较X比当前候选者更大的点,因为您已经比较并与之前的所有X值组合。这使您更接近您想要的O(n)复杂度。当然,在实际执行之前,您需要检查Y维度并完全限定要合并的点数。不要只使用X距离做出合并决定。