受约束的矩形形状的强制定向布局

时间:2016-12-16 18:46:46

标签: javascript geometry rectangles force-layout

我需要均匀分布一组轴对齐的滑动矩形,这些矩形受最大宽度/高度和一些水平/垂直坐标的约束,这取决于滑动形状本身的位置。矩形在一个方向上受约束,可以沿另一个轴滑动,也可以不重叠而不会跨越。

这个问题基于:How to implement a constraint solver for 2-D geometry?和Spektre公认的强制驱动约束求解器提议。

整个结构像往常一样构建,矩形代表节点。

现在,我需要检查每个矩形的大小以获得正确的力计算并避免重叠,但是我很难理解力场如何应用于二维形状,以及距离如何应计算两个矩形之间的距离。也许顶点或边?

相关代码位于下面的Solver.solve()函数中,其中s.Z分别表示水平形状的高度,以及垂直形状的宽度:

for(var i=0, l=sliders.length; i<l; i++) {
    var si = sliders[i];
    for(var j=i+1, k=sliders.length; j<k; j++) {
        var sj = sliders[j];
        if(si._horizontal == sj._horizontal) {
            // longer side interaction
            if(si._horizontal == 1) {
                a0 = si.X + si.a; a1 = sj.X + sj.a;
                b0 = si.X + si.b; b1 = sj.X + sj.b;
                x0 = si.Y; x1 = sj.Y;
            } else {
                a0 = si.Y + si.a; a1 = sj.Y + sj.a;
                b0 = si.Y + si.b; b1 = sj.Y + sj.b;
                x0 = si.X; x1 = sj.X;
            }
            if(((a0 <= b1) && (b0 >= a1)) || ((a1 <= b0) && (b1 >= a0))) {
                x0 = x1 - x0;
                if((si.ia >= 0) && (x0 < 0.0) && ((fabs(si.x0) < si.Z) || (fabs(si.x0) > fabs(x0)))) si.x0 = -x0;
                if((si.ia >= 0) && (x0 > 0.0) && ((fabs(si.x1) < si.Z) || (fabs(si.x1) > fabs(x0)))) si.x1 = -x0;
                if((sj.ia >= 0) && (x0 < 0.0) && ((fabs(sj.x0) < sj.Z) || (fabs(sj.x0) > fabs(x0)))) sj.x0 = +x0;
                if((sj.ia >= 0) && (x0 > 0.0) && ((fabs(sj.x1) < sj.Z) || (fabs(sj.x1) > fabs(x0)))) sj.x1 = +x0;
            }
            // shorter side interaction
            if(si._horizontal == 1) {
                a0 = si.Y - si.Z; a1 = sj.Y + sj.Z;
                b0 = si.Y + si.Z; b1 = sj.Y + sj.Z;
                x0 = si.X; x1 = sj.X;
            } else {
                a0 = si.X - si.Z; a1 = sj.X + sj.Z;
                b0 = si.X + si.Z; b1 = sj.X + sj.Z;
                x0 = si.Y; x1 = sj.Y;
            }
            if(((a0 <= b1) && (b0 >= a1)) || ((a1 <= b0) && (b1 >= a0))) {
                if(x0 < x1) {
                    x0 += si.b; x1 += sj.a;
                } else{
                    x0 += si.a; x1 += sj.b;
                }
                x0 = x1 - x0;
                if(si.ia >= 0) {
                    var sa = this.sliders[si.ia];
                    if((sa.ia >= 0) && (x0 < 0.0) && ((fabs(sa.x0) < sa.Z) || (fabs(sa.x0) > fabs(x0)))) sa.x0 = -x0;
                    if((sa.ia >= 0) && (x0 > 0.0) && ((fabs(sa.x1) < sa.Z) || (fabs(sa.x1) > fabs(x0)))) sa.x1 = -x0;
                }
                if(sj.ia >= 0) {
                    var sa = sliders[sj.ia];
                    if((sa.ia >= 0) && (x0 < 0.0) && ((fabs(sa.x0) < sa.Z) || (fabs(sa.x0) > fabs(x0)))) sa.x0 = +x0;
                    if((sa.ia >= 0) && (x0 > 0.0) && ((fabs(sa.x1) < sa.Z) || (fabs(sa.x1) > fabs(x0)))) sa.x1 = +x0;
                }
            }
        }
    }
}
// set x0 as 1D vector to closest perpendicular neighbour before and x1 after
for(var i=0, l=sliders.length; i<l; i++) {
    var si = sliders[i];
    for(var j=i+1, k=sliders.length; j<k; j++) {
        var sj = sliders[j];
        if(si._horizontal != sj._horizontal) {
            // skip ignored sliders for this
            var ignore = false;
            for(var n=0, m=si.ic.length; n<m; n++) {
                if(si.ic[n] == j) {
                    ignore = true;
                    break;
                }
            }
            if(ignore === true) continue;
            if(si._horizontal == 1) {
                a0 = si.X + si.a; a1 = sj.X - sj.Z;
                b0 = si.X + si.b; b1 = sj.X + sj.Z;
                x0 = si.Y;
            } else {
                a0 = si.Y + si.a; a1 = sj.Y - sj.Z;
                b0 = si.Y + si.b; b1 = sj.Y + sj.Z;
                x0 = si.X;
            }
            if(((a0 <= b1) && (b0 >= a1)) || ((a1 <= b0) && (b1 >= a0))){
                if(si._horizontal == 1) {
                    a1 = sj.Y + sj.a;
                    b1 = sj.Y + sj.b;
                        } else {
                    a1 = sj.X + sj.a;
                    b1 = sj.X + sj.b;
                }
                a1 -= x0; b1 -= x0;
                if(fabs(a1) < fabs(b1)) x0 = -a1; else x0 = -b1;
                if((si.ia >= 0) && (x0 < 0.0) && ((fabs(si.x0) < si.Z) || (fabs(si.x0) > fabs(x0)))) si.x0 = +x0;
                if((si.ia >= 0) && (x0 > 0.0) && ((fabs(si.x1) < si.Z) || (fabs(si.x1) > fabs(x0)))) si.x1 = +x0;
                if(sj.ia < 0) continue;
                var sa = sliders[sj.ia];
                if((sa.ia >= 0) && (x0 < 0.0) && ((fabs(sa.x0) < sa.Z) || (fabs(sa.x0) > fabs(x0)))) sa.x0 = -x0;
                if((sa.ia >= 0) && (x0 > 0.0) && ((fabs(sa.x1) < sa.Z) || (fabs(sa.x1) > fabs(x0)))) sa.x1 = -x0;
            }
        }
    }
}

矩形形状的力计算如何从力场得到均匀分布,即矩形之间的距离最大可能?想想矩形真的很热,并且必须最多间隔,相对于它们的自定义x / y约束。

非常感谢任何帮助。

编辑:

示例:https://plnkr.co/edit/3xGmAKsly2qCGMp3fPrJ?p=preview

1 个答案:

答案 0 :(得分:1)

如果我得到这个问题, OP 会询问驱动滑块的规则集,以便最终的模拟状态导致有效的解决方案。

所以这是我的方法,它实际上是从 OP 中的链接答案重新解释我的求解器代码,但它不适合那里,因为我已达到30 KByte限制,我觉得它需要一点更多解释然后只是注释代码所以这里是:

  1. <强>力

    为了尽可能确保相等的间距,你需要稍微改变一下规则(除了真实的物理学),这样你才能算出最接近驱动力的障碍而不是现实世界中的所有障碍。此外,力仅受距离的影响,并且也不会受到大多数物理力所影响的接触/重叠区域的影响(包括静电)。

    因此,在i-th滑块(黄色)的迭代过程中,找到所有4个方向上最近障碍物的距离(红色):

    closest neighbors

    计算必须按距离缩放的驱动力。无论是线性还是不线性,但从左/右或上/下均匀分布的障碍物应该是合力0。缩放主要改变动态行为。只有在约束阻止运动达到均匀间距的状态下,最终结果才会受到影响。所以你可以使用以下任何一个:

    F = c * (d1-d0)
    F = c * (d1^2 - d0^2)
    F = c * (1/d1^2 - 1/d0^2)
    

    c是某个幅度系数,而d0,d1是同一轴上最近的2个障碍距离。

    现在你将获得2个力(每个轴一个)

    • Fx - 水平轴力
    • Fy - 垂直轴力

    简而言之就是它。但是约束存在问题。例如,选定的滑块(黄色)是垂直的。这意味着它只能在x轴上移动。所以你把Fx驱动力放到了它上面。 Fy力应该驱动它的父滑块(蓝色),它是水平的,可以在y轴上移动(如果不是粗糙的话)。

    这引入了一个问题,因为滑块也可以有自己的Fy,因此你应该始终只从每一侧选择最强的力。这意味着您需要记住每侧的距离/力,并始终选择最小距离或|最高|来自各方的力量。

    这就是x0,x1变量来自它们的位置,它们保持移动能力轴到最近障碍物(来自包括儿童)的最小距离,并且仅在所有滑块的计算转换为Fx,Fy驱动力之后。

    为了检测邻居和碰撞,我认识到3种类型的交互

    • 垂直,位于水平和垂直滑块之间(图像上的底部交互)
    • 宽平行,位于相同类型的两个滑块之间,与其较长边接触(图像上的左,右交互)
    • 短平行,位于相同类型的两个滑块之间,与其较短边接触(图像上的顶部交互)
  2. 限制和系数

    我还介绍了一些速度限制和系数(不仅仅是倾倒以避免振荡)。速度和加速度限制会影响解决方案的时间和稳定性。使用它们还有另一个原因(我不这样做),那就是保持滑块的顺序,这样它们就不会相互跳过。这可以通过简单地限制最高速度来完成,因此每次迭代任何滑块都不会移动超过滑块厚度的一半。因此,如果发生碰撞,碰撞程序将会启动。

    我在我的代码中跳过它,因为我的配置已设置,因此滑块的顺序与其索引号一致。因此,如果我检测到任何滑块位于某个测试滑块的左侧,而其索引较高,则意味着它已跳过并且我通过恢复最后位置来处理它...这是便宜的黑客攻击并且无法在复杂的集合中工作链中的许多儿童滑块不仅仅是一个。

  3. <强>反馈

    由于受到限制,有时你的驱动力可能会丢失(前往固定或卡住的父滑块),以防这种行为破坏结果,你应该反过来传播相反的力量,它也会引起邻居。对于简单的构造,这不是必要的,因为它已经包含它的驱动力,但对于更复杂的儿童链,它可能构成可能的威胁(但这是我疯狂的想法,我可能在这方面是错的)。 在本质上,这是通过整合来自所有邻近物体的力而不仅仅是最接近的物体来提供的,但这会导致我认为不相等的间距。