【问题标题】:algorithm to spread items around a circle near their start positions将项目围绕开始位置附近的圆圈展开的算法
【发布时间】:2014-01-12 22:31:57
【问题描述】:

我正在制作一个图表,该图表在一个圆圈周围有许多项目。对于每个项目,我都有一个弧度,允许我将其放置在正确的位置 - 但我有重叠,我想将项目展开,但让它们保持靠近(尽可能靠近)它们的原始位置。

为避免重叠,每个项目必须与任何相邻项目至少成 7 度角。圆圈周围的所有项目总是有足够的空间 - 最多有 20 个项目,每个项目有可能有 18 度的分离(不会发生这种情况),但我很可能会在一个区域中最多有 6 或 8 个项目,并且可能有多个集群。

大多数情况下,我会有 10 到 12 个项目,但为了便于说明 - 假设我有 5 个相隔 1 度的项目:[1,2,3,4,5]。理想情况下,结果将是 [349,356,3,10,17] - 每个项目与任何其他项目成 7 度角,项目 3 保持不变,但所有项目都尽可能靠近它们的原始位置。

当然,当我拥有所有 20 件物品时 - 我冒着将一件物品移到另一件物品中的风险。举一个类似的例子:[340,1,2,3,4,5]。与上面相同 - 除了将 1 移动到 346 会导致另一个重叠。

鉴于项目弧度的完整列表,有谁知道完成我想要做的事情的方法?

我似乎无法弄清楚如何完成它。

【问题讨论】:

  • 360度除以项目数?
  • 均匀间隔很容易,并不代表我正在显示的数据。这些项目相对于它们所代表的数据进行定位。我需要它们保持在指定位置上或附近,但要分散开,以免重叠。

标签: javascript algorithm


【解决方案1】:

我用不同的方法解决了你的问题。

这个想法是一次将一个对象添加到圆圈中。
每个对象都在其周围定义了一个碰撞区域。

添加新对象时,会检查它是否与现有对象发生冲突。
如果发生碰撞,则会将新对象添加到“碰撞组”中,其中包含需要进行相对位置调整以避免相互重叠的所有对象。
如果未检测到重叠,则会创建一个仅包含新对象的新碰撞组。

在接受一个新对象后,一个组将覆盖以对象重心为中心的圆的一部分,其宽度刚好足以容纳所有对象而不会重叠。

每次碰撞组增长时,都会检查所有其他组的重叠情况,并与检测到的第一个相交组合并。
对结果组重复该过程,直到无法再进行合并。

添加所有对象后,将在每个组内计算位置调整以均匀分布对象。


看小提琴here

var CollisionResolver = function (radius)
{
    this.radius = radius || 3.5; // degrees
    this.groups = [];
    this.collision = {};
    this.objects = {};
    this.num_obj = 0;
}

CollisionResolver.prototype = 
{
    // add an object to the circle
    add: function (obj)
    {
        function sort_group ()
        {
            group.elem.sort(function(a,b) {return a.pos>b.pos; });
            var middle = 0;
            for (var i = 0; i != group.elem.length ; i++) middle += group.elem[i].pos;
            middle /=  group.elem.length;
            var range = this.radius * group.elem.length;
            group.min = middle-range;
            group.max = middle+range;

            // see if the expanded group now overlaps another
            for (var g = 0 ; g != this.groups.length ; g++)
            {
                var group2 = this.groups[g];
                if (group2 == group) continue;
                for (var offset = 0 ; offset <720 ; offset += 360)
                {
                    if (  (group2.max+offset > group.min)
                       && (group2.min+offset < group.max))
                    {
                        // merge groups
                        for (var i = 0 ; i != group2.elem.length ; i++)
                            group2.elem[i].pos += offset;
                        group.elem = group.elem.concat(group2.elem);
                        this.groups.splice (g, 1);

                        // try again with the merged group
                        sort_group.call (this,group);
                        return;
                    }
                }
            }
        }

        // store the object
        obj.id = this.num_obj;
        this.objects[this.num_obj++] = obj;

        // see if the object falls within a collision group
        for (var g in this.groups)
        {
            var group = this.groups[g];
            for (var offset = 0 ; offset <720 ; offset += 360)
            {
                if (  (obj.pos+offset+this.radius > group.min)
                   && (obj.pos+offset-this.radius < group.max))
                {
                    // insert the object into the collision group
                    obj.pos += offset;
                    group.elem.push (obj);
                    sort_group.call (this, group);
                    return;
                }
            }
        }

        // create a new singleton collision group
        var group = { 
            elem: [obj],
            min :obj.pos-this.radius,
            max :obj.pos+this.radius };
        this.groups.push (group);
    },

    resolve: function ()
    {
        // spread the objects inside each group
        var groups = this.groups;
        for (var i = 0 ; i != groups.length ; i++)
        {
            var group = groups[i];
            for (var o = 0 ; o != group.elem.length ; o++)
            {
                group.elem[o].pos = (group.min + (2*o+1) * this.radius + 360) % 360;
            }
        }

        // return the positions
        var res = [];
        for (var i = 0 ; i != this.num_obj ; i++) res.push (this.objects[i].pos);
        return res;
    }
}

function spread (positions, radius)
{
    var collider = new CollisionResolver (radius);

    // initialize object positions
    for (var i = 0; i != positions.length ; i++)
    {
        collider.add ({ pos:positions[i]} );
    }

    // resolve collisions
    return collider.resolve();  
}

它的边缘仍然有点粗糙(我不太确定 360°->0 过渡处理是否真的万无一失),但它似乎在大多数情况下都能胜任。我会说这对于概念验证来说已经足够了。

此算法不会强制您要求原始位置和调整位置之间的最大(角度)距离。另一方面,只要有足够的空间将所有对象放入圆圈中,它就保证不会重叠。

【讨论】:

  • 这很好。当我在接下来的几周内有时间时,我将重新编写我的代码来尝试这个。如果它和你的小提琴一样好用,我会改变我接受的答案。
  • 已接受的答案已更改 - 这是一个完美的结果。谢谢!
【解决方案2】:

从您的项目中构建一个 { item, itemAngle, itemDisplayAngle } 数组。
最初将 itemDisplayAngle 设置为 itemAngle。
按 itemAngle 对数组进行排序。
然后循环遍历数组,如果两个连续的项目之间的距离小于7,如果房间没有被占用,则将项目7向右移动。 注意,距离必须为 %360,如果角度 + 7 > 360,则必须注意数组的第一项。
重复上述步骤,直到不执行任何操作。

编辑:要获得最小传播,解决方案非常相似,只是有点难以解释:如果传播 但当然,您必须检查您现在是否可以在您的左侧和右侧移动项目。我认为,如果您不能使用半价差,您应该测试是否可以将右侧项目移至右侧,或将左侧项目移至左侧。这样你就有了最佳的位置。

【讨论】:

  • 这只会在一个方向上移动项目。它不会使它们尽可能接近其原始起始位置。您的解决方案将 item1 保留在原位,而 item 5 移动了 28 度。我正在寻找的解决方案将项目 5 放在 14 度之外。请参阅我的第一个示例。
  • 你的编辑是我整晚都在研究的方法。我终于明白了。这不是最好的方法...
猜你喜欢
  • 2012-05-13
  • 2015-08-27
  • 1970-01-01
  • 1970-01-01
  • 2019-04-14
  • 2020-06-18
  • 1970-01-01
  • 1970-01-01
  • 2019-04-25
相关资源
最近更新 更多