jjzjj

javascript - 如何实现二维几何的约束求解器?

coder 2024-07-16 原文

我有一组金属滑动件,它们按以下方式约束到x和y轴:



我需要最大化受同一滑块约束的所有零件之间的水平距离,以及滑块与滑块本身之间的垂直距离。如何解决呢?

任何可以解决该问题的建议都将不胜感激。

我首先看了一些非常强大的库,例如cassowary和jsLPSolver,但是我在理解核心算法以及如何检查约束的可行性以及如何对可能的解决方案进行排名时遇到了一些麻烦。

如何在JavaScript中为二维几何约束求解器实现一个(简单)存根,以解决上述问题?

编辑:

我有以下输入数据:

maxW = 300, maxH = 320

各个部分的定义如下(不是强制性的,每个解决方案都可以接受):
slidingPiece = [pX, pY, width, height, anchorPoint, loopDistance];

我将尝试解释“最大化”下的含义。

水平间距:

a0-b1,b1-b2,b2-b4,b4-b5和b5-maxX将是相同的,即最大X除以最大垂直相交件数+ 1(5)。
然后,b1-b3和b3-b5将由可用的剩余空间确定。

垂直间距:

b1-a3,a3-a4和a0-b5相同。理想情况下,a0-b3,b3-b4,a2-b2,b4-a3和b2-a4也是相同的值。最大化a1-b4和b3-a2与最大化b3-b4相同。同样适用于a2-b2和b4-a3:距离b2-b4将是最大负值。

因此,我需要最大程度地延长每个滑动块与他在Y约束之上或之下的最近距离。

此问题的二维几何表示表明,水平间距取决于 anchor 的垂直距离(由于 anchor 定件的垂直交点),而距离又取决于件本身的水平位置。例如,想想b2在上面短一些。在这种情况下,b1和b2不再相交并且将变为相同的x值,即max X除以4。

在其他一些情况下,例如b2在上面的部分中更长-会越过 anchor a2,则它应与a1隔开。这是因为会有一组解决方案,一些可行,而另一些则没有,例如,将破坏全局最大Y约束。

最佳答案

我会尝试类似于this的现场方法。

  • 每个滑块将收回所有滑块

    力按距离^ 2缩放,就像它们都将在彼此之间具有相同极性的电荷或 Spring 一样。
  • 最重要的是添加摩擦力,它由速度缩放

    空气v^2或液体v^3
  • 并不重要
  • 实现运动学约束

    对于仅水平滑动和垂直滑动,应该非常容易。
  • 做物理模拟,等到收敛到稳定状态v=~0

    如果按本地最小/最大值,请摇动整个对象,或随机安排整个对象,然后重试。您也可以这样做以获得其他解决方案。

  • [Edit4] C++求解器示例
  • 表示滑块系统的结构/类

    为了简化以后的代码,我将不支持闭环或双 anchor 。这就是为什么i1滑块(最右边)没有固定到任何东西(只会提供力场)的原因。
    我最终得到了以下滑块定义:



    查看class _slider的源以获取更多信息。
  • 渲染

    短划线表示固定的滑块。银色表示水平,浅绿色表示垂直,鼠标选择黄色。可能稍后会亮起红色表示某种错误/卡住或某种出于调试目的的东西。对于力场求解器,我有时将场强添加为红蓝色标度,但不确定是否在此实现。

    为简单起见,我将不实现缩放/平移功能,因为您的尺寸便于直接渲染而无需变换。

  • 实现初始设置

    sliders sys;
    int i0,i1,a0,a1,a2,a3,a4,b1,b2,b3,b4,b5;
    sys.slider_beg();//ia,ib,   x,    y,    a0,    a1,    b0,    b1,_horizontal
    i0=sys.slider_add(-1,-1, 25.0, 25.0,  -5.0, 405.0,   0.0,   0.0, 0);
    a0=sys.slider_add(i0,-1,  0.0,  0.0,   0.0, 400.0,   0.0,   0.0, 1);
    a1=sys.slider_add(i0,-1,  0.0,100.0,   0.0, 400.0,   0.0,   0.0, 1);
    a2=sys.slider_add(i0,-1,  0.0,200.0,   0.0, 400.0,   0.0,   0.0, 1);
    a3=sys.slider_add(i0,-1,  0.0,300.0,   0.0, 400.0,   0.0,   0.0, 1);
    a4=sys.slider_add(i0,-1,  0.0,400.0,   0.0, 400.0,   0.0,   0.0, 1);
    b1=sys.slider_add(a0,a2, 20.0,  0.0,   0.0, 125.0, 125.0, 250.0, 0);
    b2=sys.slider_add(a3,-1, 40.0,  0.0, -70.0,  30.0,   0.0,   0.0, 0);
    b3=sys.slider_add(a1,-1, 60.0,  0.0, -70.0,  30.0,   0.0,   0.0, 0);
    b4=sys.slider_add(a2,-1, 80.0,  0.0, -30.0,  70.0,   0.0,   0.0, 0);
    b5=sys.slider_add(a3,a1,100.0,  0.0,-125.0,   0.0,-125.0,-250.0, 0);
    i1=sys.slider_add(-1,-1,425.0, 25.0,  -5.0, 405.0,   0.0,   0.0, 0);
    sys.slider_end();
    

    其中ia是父级索引​​,而ib是子级索引(滑块类本身将ib保留为父级索引,但是由于您需要链接到尚不存在的项,因此初始化时会感到困惑,因此ib转换在sys.add函数中进行处理) 。 sys是保存整个内容的类,sys.add只是向其中添加新的滑块,并返回从零开始计数的索引。 x,y是相对于父级的相对位置。

    为了减轻编码量,此设置不得与约束冲突。此设置的概述在先前的项目符号中。

    请注意,垂直滑块的顺序必须从左到右,水平滑块的顺序从上到下,以确保正确的约束功能。
  • 鼠标交互

    只需简单的滑块移动即可调试和调整初始设置值。和或处理卡住的情况。您需要处理鼠标事件,如果尚未编辑,请选择最近的滑块。如果按下鼠标按钮,将选定的滑块移动到鼠标位置...
  • body 约束/互动

    我对此进行了简化,因此我刚刚创建了一个谓词函数,该谓词函数用于指定的滑块,并且如果它或它的任何子代/ anchor 定与定义的约束冲突,它将返回。这样就更容易编码和调试,然后更新位置以匹配实际约束。

    用法则是更多代码。首先存储更新滑块的实际位置。然后将滑块更新到新位置/状态。此后,如果不满足约束条件,则停止实际的滑块速度并恢复其原始位置。

    这会慢一些,但我懒得编写完整的约束更新程序(该代码可能会变得非常复杂...)。

    我认识到2个平行和垂直的互动。平行是直接的。但是,垂直线是滑块边缘与其附近的垂直滑块之间的相互作用,不包括在初始状态下已经相交的滑块(a,b anchor 定或刚好相交)。因此,我在开始时创建了一个相交滑块(ic)的列表,该交互将被忽略。
  • 物理模拟

    简单的Newton - D'Alembert physics for non relativistic speeds就可以了。只需在每次迭代中将加速度ax,ay设置为磁场强度和摩擦即可。
  • 字段求解器

    这是一组规则/等式,用于设置每个滑块的仿真加速度以收敛到解。最后我得到了静电回缩力F = -Q/r^2和线性衰减速度。还实现了绝对速度和加速度限制器,以避免数值问题。

    为了增加求解时间和稳定性,我添加了精确的控制模式,当滑块的总最大速度降低时,电荷会降低。

  • 这里是完整的 C++ / VCL 类代码:

    //---------------------------------------------------------------------------
    //--- Sliders solver ver: 1.01 ----------------------------------------------
    //---------------------------------------------------------------------------
    #ifndef _sliders_h
    #define _sliders_h
    //---------------------------------------------------------------------------
    #include <math.h>
    #include "list.h"   // linear dynamic array template List<T> similar to std::vector
    //---------------------------------------------------------------------------
    const double _slider_w   =   3.00;  // [px] slider half width (for rendering)
    const double _slider_gap =   4.00;  // [px] min gap between sliders (for colisions)
    const double _acc_limit=   100.00;  // [px/s^2]
    const double _vel_limit=   100.00;  // [px/s]
    const double _friction =     0.90;  // [-]
    const double _charge   =250000.00;  // [px^3/s^2]
    //---------------------------------------------------------------------------
    class _slider   // one slider (helper class)
        {
    public:
        // properties
        double x,y;             // actual relative pos
        bool _horizontal;       // orientation
        double a0,a1;           // slider vertexes 0 is anchor point
        double b0,b1;           // anchor zone for another slider
        int ia;                 // -1 for fixed or index of parrent slider
        int ib;                 // -1 or index of parrent slider
        // computed
        List<int> ic;           // list of slider indexes to ignore for perpendicular constraints
        double a,b;             // force field affected part
        double X,Y;             // actual absolute position
        double vx,vy,ax,ay;     // actual relative vel,acc
        // temp
        int flag;               // temp flag for simulation
        double x0,x1;           // temp variables for solver
        // constructors (can ignore this)
        _slider()           {}
        _slider(_slider& a) { *this=a; }
        ~_slider()          {}
        _slider* operator = (const _slider *a) { *this=*a; return this; }
        //_slider* operator = (const _slider &a) { ...copy... return this; }
        };
    //---------------------------------------------------------------------------
    class sliders   // whole slider system main class
        {
    public:
        List<_slider> slider;           // list of sliders
    
        double vel_max;                 // max abs velocity of sliders for solver precision control
        double charge;                  // actual charge of sliders for solve()
        int    mode;                    // actual solution precision control mode
    
        // constructors (can ignore this)
        sliders();
        sliders(sliders& a) { *this=a; }
        ~sliders()          {}
        sliders* operator = (const sliders *a) { *this=*a; return this; }
        //sliders* operator = (const sliders &a) { ...copy... return this; }
    
        // VCL window API variables (can ignore this)
        double mx0,my0,mx1,my1; // last and actual mouse position
        TShiftState sh0,sh1;    // last and actual mouse buttons and control keys state
        int sel;
    
        // API (this is important stuff)
        void slider_beg(){ slider.num=0; }  // clear slider list
        int  slider_add(int ia,int ib,double x,double y,double a0,double a1,double b0,double b1,bool _h); // add slider to list
        void slider_end();              // compute slider parameters
        bool constraints(int ix);       // return true if constraints hit
        void positions();               // recompute absolute positions
        void update(double dt);         // update physics simulation with time step dt [sec]
        void solve(bool _init=false);   // set sliders accelerations to solve this
        void stop();                    // stop all movements
        // VCL window API for interaction with GUI (can ignore this)
        void mouse(int x,int y,TShiftState sh);
        void draw(TCanvas *scr);
        };
    //---------------------------------------------------------------------------
    sliders::sliders()
        {
        mx0=0.0; my0=0.0;
        mx1=0.0; my1=0.0;
        sel=-1;
        }
    //---------------------------------------------------------------------------
    int sliders::slider_add(int ia,int ib,double x,double y,double a0,double a1,double b0,double b1,bool _h)
        {
        _slider s; double q;
        if (a0>a1) { q=a0; a0=a1; a1=q; }
        if (b0>b1) { q=b0; b0=b1; b1=q; }
        s.x=x; s.vx=0.0; s.ax=0.0;
        s.y=y; s.vy=0.0; s.ay=0.0;
        s.ia=ia; s.a0=a0; s.a1=a1;
        s.ib=-1; s.b0=b0; s.b1=b1;
        s.ic.num=0;
        if ((ib>=0)&&(ib<slider.num)) slider[ib].ib=slider.num;
        s._horizontal=_h;
        s.a=a0; // min
        if (s.a>a1) s.a=a1;
        if (s.a>b0) s.a=b0;
        if (s.a>b1) s.a=b1;
        s.b=a0; // max
        if (s.b<a1) s.b=a1;
        if (s.b<b0) s.b=b0;
        if (s.b<b1) s.b=b1;
        slider.add(s);
        return slider.num-1;
        }
    //---------------------------------------------------------------------------
    void sliders::slider_end()
        {
        int i,j;
        double a0,a1,b0,b1,x0,x1,w=_slider_gap;
        _slider *si,*sj;
        positions();
        // detect intersecting sliders and add them to propriet ic ignore list
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
         for (sj=si+1   ,j=i+1;j<slider.num;j++,sj++)
          if (si->_horizontal!=sj->_horizontal)
            {
            if (si->_horizontal)
                {
                a0=si->X+si->a; a1=sj->X-w;
                b0=si->X+si->b; b1=sj->X+w;
                x0=si->Y;       x1=sj->Y;
                }
            else{
                a0=si->Y+si->a; a1=sj->Y-w;
                b0=si->Y+si->b; b1=sj->Y+w;
                x0=si->X;       x1=sj->X;
                }
            if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
             if ((x0>x1+sj->a-w)&&(x0<x1+sj->b+w))
                {
                si->ic.add(j);
                sj->ic.add(i);
                }
            }
        }
    //---------------------------------------------------------------------------
    bool sliders::constraints(int ix)
        {
        int i,j;
        double a0,a1,b0,b1,x0,x1,x,w=_slider_gap;
        _slider *si,*sj,*sa,*sb,*s;
        s=slider.dat+ix;
        // check parallel neighbors overlapp
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
         if ((i!=ix)&&(si->_horizontal==s->_horizontal))
            {
            if (s->_horizontal)
                {
                a0=s->X+s->a; a1=si->X+si->a;
                b0=s->X+s->b; b1=si->X+si->b;
                x0=s->Y;      x1=si->Y;
                }
            else{
                a0=s->Y+s->a; a1=si->Y+si->a;
                b0=s->Y+s->b; b1=si->Y+si->b;
                x0=s->X;      x1=si->X;
                }
            if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
                {
                if ((i<ix)&&(x0<x1+w)) return true;
                if ((i>ix)&&(x0>x1-w)) return true;
                }
            }
        // check perpendicular neighbors overlapp
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
         if ((i!=ix)&&(si->_horizontal!=s->_horizontal))
            {
            // skip ignored sliders for this
            for (j=0;j<s->ic.num;j++)
             if (s->ic[j]==i) { j=-1; break; }
              if (j<0) continue;
            if (s->_horizontal)
                {
                a0=s->X+s->a; a1=si->X-w;
                b0=s->X+s->b; b1=si->X+w;
                x0=s->Y;      x1=si->Y;
                }
            else{
                a0=s->Y+s->a; a1=si->Y-w;
                b0=s->Y+s->b; b1=si->Y+w;
                x0=s->X;      x1=si->X;
                }
            if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
             if ((x0>x1+si->a-w)&&(x0<x1+si->b+w))
              return true;
            }
        // conflict a anchor area of parent?
        if (s->ia>=0)
            {
            si=slider.dat+s->ia;
            if (s->_horizontal)
                {
                x0=si->Y+si->a0;
                x1=si->Y+si->a1;
                x=s->Y;
                }
            else{
                x0=si->X+si->a0;
                x1=si->X+si->a1;
                x=s->X;
                }
            if (x<x0+w) return true;
            if (x>x1-w) return true;
            }
        // conflict b anchor area of parent?
        if (s->ib>=0)
            {
            si=slider.dat+s->ib;
            if (si->_horizontal)
                {
                x0=si->X+si->b0;
                x1=si->X+si->b1;
                x=s->X;
                }
            else{
                x0=si->Y+si->b0;
                x1=si->Y+si->b1;
                x=s->Y;
                }
            if (x<x0+w) return true;
            if (x>x1-w) return true;
            }
        // conflict b anchor area with childs?
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
         if ((i!=ix)&&(si->ib==ix))
            {
            if (s->_horizontal)
                {
                x0=s->X+s->b0;
                x1=s->X+s->b1;
                x=si->X;
                }
            else{
                x0=s->Y+s->b0;
                x1=s->Y+s->b1;
                x=si->Y;
                }
            if (x<x0+w) return true;
            if (x>x1-w) return true;
            }
    
        // check childs too
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
         if ((i!=ix)&&(si->ia==ix))
          if (constraints(i)) return true;
        return false;
        }
    //---------------------------------------------------------------------------
    void sliders::positions()
        {
        int i,e;
        _slider *si,*sa;
        // set flag = uncomputed
        for (si=slider.dat,i=0;i<slider.num;i++,si++) si->flag=0;
        // iterate until all sliders are computed
        for (e=1;e;)
         for (e=0,si=slider.dat,i=0;i<slider.num;i++,si++)
          if (!si->flag)
            {
            // fixed
            if (si->ia<0)
                {
                si->X=si->x;
                si->Y=si->y;
                si->flag=1;
                continue;
                }
            // a anchored
            sa=slider.dat+si->ia;
            if (sa->flag)
                {
                si->X=sa->X+si->x;
                si->Y=sa->Y+si->y;
                si->flag=1;
                continue;
                }
            e=1; // not finished yet
            }
        }
    //---------------------------------------------------------------------------
    void sliders::update(double dt)
        {
        int i;
        _slider *si,*sa;
        double x,X;
        // D'Lamnbert integration
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
         if (si->_horizontal)
            {
            x=si->y; si->vy+=si->ay*dt;     // vel = Integral(acc*dt)
                     si->vy*=_friction;     // friction k*vel
            X=si->Y; si->y +=si->vy*dt;     // pos = Integral(vel*dt)
            positions();                    // recompute childs
            if ((si->ia<0)||(constraints(i))) // if fixed or constraint hit (stop and restore original position)
                {
                si->vy=0.0;
                si->y =x;
                si->Y =X;
                positions();                // recompute childs
                }
            }
        else{
            x=si->x; si->vx+=si->ax*dt;     // vel = Integral(acc*dt)
                     si->vx*=_friction;     // friction k*vel
            X=si->X; si->x +=si->vx*dt;     // pos = Integral(vel*dt)
            positions();                    // recompute childs
            if ((si->ia<0)||(constraints(i))) // if fixed or constraint hit (stop and restore original position)
                {
                si->vx=0.0;
                si->x =x;
                si->X =X;
                positions();                // recompute childs
                }
            }
        }
    //---------------------------------------------------------------------------
    void sliders::solve(bool _init)
        {
        int i,j,k;
        double a0,a1,b0,b1,x0,x1;
        _slider *si,*sj,*sa;
        // init solution
        if (_init)
            {
            mode=0;
            charge=_charge;
            }
        // clear accelerations and compute actual max velocity
        vel_max=0.0;
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
            {
            si->ax=0.0;
            si->ay=0.0;
            x0=fabs(si->vx); if (vel_max<x0) vel_max=x0;
            x0=fabs(si->vy); if (vel_max<x0) vel_max=x0;
            }
        // precision control of solver
        if ((mode==0)&&(vel_max>25.0)) { mode++; }                  // wait until speed raises
        if ((mode==1)&&(vel_max<10.0)) { mode++; charge*=0.10; }    // scale down forces to lower jitter
        if ((mode==2)&&(vel_max< 1.0)) { mode++; charge*=0.10; }    // scale down forces to lower jitter
        if ((mode==3)&&(vel_max< 0.1)) { mode++; charge =0.00; stop(); } // solution found
        // set x0 as 1D vector to closest parallel neighbor before and x1 after
        for (si=slider.dat,i=0;i<slider.num;i++,si++) { si->x0=0.0; si->x1=0.0; }
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
         for (sj=si+1   ,j=i+1;j<slider.num;j++,sj++)
          if (si->_horizontal==sj->_horizontal)
            {
            // longer side interaction
            if (si->_horizontal)
                {
                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)<_slider_gap)||(fabs(si->x0)>fabs(x0)))) si->x0=-x0;
                if ((si->ia>=0)&&(x0>0.0)&&((fabs(si->x1)<_slider_gap)||(fabs(si->x1)>fabs(x0)))) si->x1=-x0;
                if ((sj->ia>=0)&&(x0<0.0)&&((fabs(sj->x0)<_slider_gap)||(fabs(sj->x0)>fabs(x0)))) sj->x0=+x0;
                if ((sj->ia>=0)&&(x0>0.0)&&((fabs(sj->x1)<_slider_gap)||(fabs(sj->x1)>fabs(x0)))) sj->x1=+x0;
                }
            // shorter side interaction
            if (si->_horizontal)
                {
                a0=si->Y-_slider_gap; a1=sj->Y+_slider_gap;
                b0=si->Y+_slider_gap; b1=sj->Y+_slider_gap;
                x0=si->X;             x1=sj->X;
                }
            else{
                a0=si->X-_slider_gap; a1=sj->X+_slider_gap;
                b0=si->X+_slider_gap; b1=sj->X+_slider_gap;
                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)
                    {
                    sa=slider.dat+si->ia;
                    if ((sa->ia>=0)&&(x0<0.0)&&((fabs(sa->x0)<_slider_gap)||(fabs(sa->x0)>fabs(x0)))) sa->x0=-x0;
                    if ((sa->ia>=0)&&(x0>0.0)&&((fabs(sa->x1)<_slider_gap)||(fabs(sa->x1)>fabs(x0)))) sa->x1=-x0;
                    }
                if (sj->ia>=0)
                    {
                    sa=slider.dat+sj->ia;
                    if ((sa->ia>=0)&&(x0<0.0)&&((fabs(sa->x0)<_slider_gap)||(fabs(sa->x0)>fabs(x0)))) sa->x0=+x0;
                    if ((sa->ia>=0)&&(x0>0.0)&&((fabs(sa->x1)<_slider_gap)||(fabs(sa->x1)>fabs(x0)))) sa->x1=+x0;
                    }
                }
            }
        // set x0 as 1D vector to closest perpendicular neighbor before and x1 after
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
         for (sj=si+1   ,j=i+1;j<slider.num;j++,sj++)
          if (si->_horizontal!=sj->_horizontal)
            {
            // skip ignored sliders for this
            for (k=0;k<si->ic.num;k++)
             if (si->ic[k]==j) { k=-1; break; }
              if (k<0) continue;
            if (si->_horizontal)
                {
                a0=si->X+si->a; a1=sj->X-_slider_w;
                b0=si->X+si->b; b1=sj->X+_slider_w;
                x0=si->Y;
                }
            else{
                a0=si->Y+si->a; a1=sj->Y-_slider_w;
                b0=si->Y+si->b; b1=sj->Y+_slider_w;
                x0=si->X;
                }
            if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
                {
                if (si->_horizontal)
                    {
                    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)<_slider_gap)||(fabs(si->x0)>fabs(x0)))) si->x0=+x0;
                if ((si->ia>=0)&&(x0>0.0)&&((fabs(si->x1)<_slider_gap)||(fabs(si->x1)>fabs(x0)))) si->x1=+x0;
                if (sj->ia<0) continue;
                sa=slider.dat+sj->ia;
                if ((sa->ia>=0)&&(x0<0.0)&&((fabs(sa->x0)<_slider_gap)||(fabs(sa->x0)>fabs(x0)))) sa->x0=-x0;
                if ((sa->ia>=0)&&(x0>0.0)&&((fabs(sa->x1)<_slider_gap)||(fabs(sa->x1)>fabs(x0)))) sa->x1=-x0;
                }
            }
        // convert x0,x1 distances to acceleration
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
            {
            // driving force F = ~ Q / r^2
            if (fabs(si->x0)>1e-10)  x0=charge/(si->x0*si->x0); else x0=0.0; if (si->x0<0.0) x0=-x0;
            if (fabs(si->x1)>1e-10)  x1=charge/(si->x1*si->x1); else x1=0.0; if (si->x1<0.0) x1=-x1;
            a0=x0+x1;
            // limit acc
            if (a0<-_acc_limit) a0=-_acc_limit;
            if (a0>+_acc_limit) a0=+_acc_limit;
            // store parallel acc to correct axis
            if (si->_horizontal) si->ay=a0;
             else                si->ax=a0;
            // limit vel (+/- one iteration overlap)
            if (si->_horizontal) x0=si->vy;
             else                x0=si->vx;
            if (x0<-_vel_limit)  x0=-_vel_limit;
            if (x0>+_vel_limit)  x0=+_vel_limit;
            if (si->_horizontal) si->vy=x0;
             else                si->vx=x0;
            }
        }
    //---------------------------------------------------------------------------
    void sliders::stop()
        {
        int i;
        _slider *si;
        for (si=slider.dat,i=0;i<slider.num;i++,si++)
            {
            si->vx=0.0;
            si->vy=0.0;
            si->ax=0.0;
            si->ay=0.0;
            }
        }
    //---------------------------------------------------------------------------
    void sliders::mouse(int x,int y,TShiftState sh)
        {
        int i,q0,q1;
        double d,dd;
        _slider *si;
        // update mouse state
        mx0=mx1; my0=my1; sh0=sh1;
        mx1=x;   my1=y;   sh1=sh;
        // slider movement with left mouse button
        q0=sh0.Contains(ssLeft);
        q1=sh1.Contains(ssLeft);
        if ((sel>=0)&&(q1))
            {
            si=slider.dat+sel;
            // stop simulation for selected slider
            si->vx=0.0;
            si->vy=0.0;
            si->ax=0.0;
            si->ay=0.0;
            // use mouse position instead
            if (si->ia>=0)
                {
                if (si->_horizontal){ d=si->y; dd=si->Y; si->y+=my1-si->Y; si->Y=my1; si->vy=0.0; si->ay=0.0; positions(); if (constraints(sel)) { si->y=d; si->Y=dd; positions(); }}
                 else               { d=si->x; dd=si->X; si->x+=mx1-si->X; si->X=mx1; si->vx=0.0; si->ax=0.0; positions(); if (constraints(sel)) { si->x=d; si->X=dd; positions(); }}
                }
            }
        // select slider (if not left mouse button used)
        if (!q1)
         for (sel=-1,d=_slider_w+1.0,si=slider.dat,i=0;i<slider.num;i++,si++)
            {
            dd=_slider_w+1.0;
            if (si->_horizontal){ if ((mx1>=si->X+si->a)&&(mx1<=si->X+si->b)) dd=fabs(my1-si->Y); }
             else               { if ((my1>=si->Y+si->a)&&(my1<=si->Y+si->b)) dd=fabs(mx1-si->X); }
            if ((dd<d)&&(dd<=_slider_w)) { sel=i; d=dd; }
            }
        }
    //---------------------------------------------------------------------------
    void sliders::draw(TCanvas *scr)
        {
        int i,j,n;
        double w=_slider_w,r,x,y,a0,a1;
        AnsiString txt;
        _slider *s;
        scr->Brush->Style=bsClear;
        #define _line(aa,bb)           \
        if (s->_horizontal)            \
            {                          \
            scr->MoveTo(s->X+aa,s->Y); \
            scr->LineTo(s->X+bb,s->Y); \
            }                          \
        else{                          \
            scr->MoveTo(s->X,s->Y+aa); \
            scr->LineTo(s->X,s->Y+bb); \
            }
        scr->Pen->Color=clSilver;
        scr->Font->Color=clWhite;
        scr->TextOutA(40,40,AnsiString().sprintf("mode %i",mode));
        scr->TextOutA(40,60,AnsiString().sprintf("vel: %.3lf [px/s]",vel_max));
        scr->TextOutA(40,80,AnsiString().sprintf("  Q: %.3lf [px^3/s^2]",charge));
        scr->Font->Color=clYellow;
        for (s=slider.dat,i=0;i<slider.num;i++,s++)
            {
            if (s->_horizontal) scr->Pen->Color=clSilver;
             else               scr->Pen->Color=clAqua;
            if (i==sel)
                {
                scr->Pen->Color=clYellow;
                txt=AnsiString().sprintf(" ix:%i ia:%i ib:%i ic:",sel,s->ia,s->ib);
                for (j=0;j<=s->ic.num;j++) txt+=AnsiString().sprintf(" %i",s->ic[j]);
                scr->TextOutA(40,100,txt);
                scr->TextOutA(40,120,AnsiString().sprintf("pos: %.1lf %.1lf [px]",s->X,s->Y));
                scr->TextOutA(40,140,AnsiString().sprintf("vel: %.3lf %.3lf [px/s]",s->vx,s->vy));
                scr->TextOutA(40,160,AnsiString().sprintf("acc: %.3lf %.3lf [px/s^2]",s->ax,s->ay));
                scr->Pen->Color=clYellow;
                }
            if (s->ia<0) scr->Pen->Style=psDash;
             else        scr->Pen->Style=psSolid;
            // a anchor loop
            x=s->X;
            y=s->Y;
            if (s->ia>=0) scr->Ellipse(x-w,y-w,x+w,y+w);
            // b anchor loop
            r=0.5*fabs(s->b1-s->b0);
            if (s->_horizontal)
                {
                x=s->X+0.5*(s->b0+s->b1);
                y=s->Y;
                scr->RoundRect(x-r,y-w,x+r,y+w,w,w);
                }
            else{
                x=s->X;
                y=s->Y+0.5*(s->b0+s->b1);
                scr->RoundRect(x-w,y-r,x+w,y+r,w,w);
                }
            // a line cutted by a anchor loop
            a0=s->a0; a1=s->a1;
            if ((s->ia>=0)&&(a0<=+w)&&(a1>=-w))
                {
                if (a0<-w) _line(s->a0,-w);
                if (a1>+w) _line( w,s->a1);
                }
            else _line(s->a0,s->a1);
            }
        scr->Font->Color=clDkGray;
        scr->Pen->Style=psSolid;
        scr->Brush->Style=bsSolid;
        #undef _line
        }
    //---------------------------------------------------------------------------
    #endif
    //---------------------------------------------------------------------------
    

    您可以忽略VCL内容,它只是与我的App窗口和呈现进行交互的API。求解器本身不需要任何东西。我使用了动态线性数组模板List<T>,因此这里提供了一些解释:
  • List<double> xxx;double xxx[];相同
  • xxx.add(5);5添加到列表的末尾
  • xxx[7]访问数组元素(安全)
  • xxx.dat[7]访问数组元素(不安全但快速直接访问)
  • xxx.num是数组
  • 的实际使用大小
  • xxx.reset()清除数组并设置xxx.num=0
  • xxx.allocate(100)100项预分配空间

  • 从bullet #3 正确初始化后,用法很简单,如下所示:
    sys.solve(true);
    for (;;)
     {
     sys.solve();
     sys.update(0.040); // just time step
     if (sys.mode==4) break; // stop if solution found or stuck
     }
    

    我不是在循环中调用计时器,而是重新绘制窗口,以便看到动画:



    断断续续是由于不均匀的 GIF 抓取采样率(不规则地跳过模拟中的某些帧)。

    您可以使用vel,acc限制,阻尼系数和模式控制if的常数来更改行为。如果您还实现了鼠标处理程序,则可以使用鼠标左键移动滑块,这样就可以摆脱卡住的情况。

    这里是一个独立的Win32演示(与 BDS2006 C++ 一起编译)。
  • Demo单击大洋红色按钮下方的慢速下载,输入4个字母的字母数字代码以开始下载,无需注册。

  • 有关求解器力计算如何工作的更多信息,请参见相关/后续质量检查:
  • Force directed layout for constrained rectangular shapes
  • 关于javascript - 如何实现二维几何的约束求解器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40817359/

    有关javascript - 如何实现二维几何的约束求解器?的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    7. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    8. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

      在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

    9. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

      我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

    10. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

      我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

    随机推荐