C++中的软体引擎

5
我试图使用SDL2在C++中制作一个基本的软体引擎。它通过将所有软体的顶点视为由相同长度和刚度的弹簧相互连接 (具有相同的弹簧常数k和长度natural_length) 来工作。为使其更加逼真,我还引入了阻尼常数c。
然而,我遇到了一个令人沮丧的问题。我已经尝试过了过去6-7小时的调试,但毫无进展。软体遇到许多奇怪的错误,我不理解。

  • 首先,“软体”根本不“柔软”。每次都会变成一堆杂乱的点。我尝试只计算相邻点之间的力量,但它仍然变成了一堆杂乱的东西。
  • 软体总是飞向左上角(原点),尽管我没有实现任何外部力。

这些错误都可以在此图像中看到 - bug

以下两个函数(它们与所有变量在同一类中,因此不需要接受任何参数)是代码的实际模拟部分。(我省略了其余代码,因为它是不必要的。)
我使用了一个SDL_Points的向量来存储每个点,以及一个Vector的向量来存储它们的速度。如果你想知道Vector是什么,它只是我创建的一个结构体,它只有两个float成员x和y。
acceleratePoints()函数为每个点分配速度和位置,而checkCollision()函数则检查与窗口边界(其宽度为scr_w,高度为scr_h)的碰撞。

    void acceleratePoints()
    {
        vector<SDL_Point> soft_body_copy=soft_body;
        vector<Vector> velocity_copy=velocity;
        for(int i=0;i<soft_body.size();++i)
        {
            for(int j=0;j<soft_body.size();++j)
            {
                if(i!=j)
                {
                    Vector d={(soft_body[j].x-soft_body[i].x)/100.0,(soft_body[j].y-soft_body[i].y)/100.0};
                    float t=atan2(d.y,d.x);
                    float disp=fabs(magnitude(d))-natural_length/100.0;
                    velocity_copy[i].x+=(k*disp*cos(t))/10000.0;
                    velocity_copy[i].y+=(k*disp*sin(t))/10000.0;
                    velocity_copy[i].x-=c*velocity_copy[i].x/100.0;
                    velocity_copy[i].y-=c*velocity_copy[i].y/100.0;
                    soft_body_copy[i].x+=velocity_copy[i].x;
                    soft_body_copy[i].y+=velocity_copy[i].y;
                }
            }
            soft_body=soft_body_copy;
            velocity=velocity_copy;
        }
    }
    void checkCollision()
    {
        for(int k=0;k<soft_body.size();++k)
        {
            if(soft_body[k].x>=scr_w||soft_body[k].x<=0)
            {
                velocity[k].x*=e;
                soft_body[k].x=soft_body[k].x>scr_w/2?scr_w-1:1;
            }
            if(soft_body[k].y>=scr_h||soft_body[k].y<=0)
            {
                velocity[k].y*=e;
                soft_body[k].y=soft_body[k].y>scr_h/2?scr_h-1:1;
            }
        }
    }
magnitude()函数返回Vector的大小。
我在图像中使用的反弹系数e、阻尼常数c和弹簧常数k的值分别为0.5、10和100。 感谢您抽出时间阅读!如有帮助将不胜感激。

编辑

这里是完整的代码,如果有人想测试它。你需要SDL和一个文件夹'img',其中包含一个'.bmp'文件'img/point.bmp'。


1
身体在静止时是什么样子的?此外,您应该考虑使用适当的运算符包装向量代数;这将有助于更容易地发现高级错误... - defube
@defube 身体是由用户输入的点集合。我输入了几个点的集合,大致呈椭圆形状。 - AvZ
为了使其正常工作,身体需要一个“休息形状”,从中计算出每个弹簧的长度。如果您允许使用随机点云,则可以使用弹簧连接所有大约1个邻居点。编辑:每个弹簧都需要自己的长度! - defube
@defube 我以近似正多边形的方式输入形状,以确保最初不会过度变形。我认为即使输入不完美,它也应该能够在变形后恢复其正多边形形状(因为它是软体)。我尝试仅使用点旁边直接连接的两个相邻点,但仍然会变得皱巴巴的。 - AvZ
@defube 每个弹簧都有相同的长度natural_length - AvZ
单一长度参数是问题所在,我会在我的回答中解释。 - defube
1个回答

4
基于Spring的软体模拟需要一个“静止状态配置”,在此状态下,没有重力或其他外部力作用时,身体将保持内部静止。
模拟中的每个弹簧都需要自己的长度。共享的“自然长度”会导致弹簧在连接点之间的输入分离不同时在身体内施加力,这似乎是您描述的问题所在。
从一组点生成软体(或“点块”)的典型方法如下:
1. 生成点集的Delaunay三角剖分(或3D中的四面体剖分)。 2. 在三角剖分的每条边上创建弹簧,使用边长作为弹簧的静止长度。 3. 模拟“重力”和其他外部力!
为简单起见,您可以仅连接所有点对,而不是生成三角剖分。
模拟本身可以分为三个步骤:
1. 清除顶点力累加器 2. 将弹簧力添加到其连接的顶点 3. 更新每个顶点的速度和位置
按照您发布的代码的精神,一个“弹簧”可能如下所示:
struct Spring
{
    int point_index[2];
    float rest_length;
};

侧边栏

在你的代码中,你可以用一个单位长度的 d 替代 t = atan2(d.y, d.x) 和接下来的 sin(t) cos(t)

float mag_d = magnitude(d); // should not become negative...
float r = (0.0f != mag_d) ? 1.0f/mag_d : 0.0f;
Vector d_hat{d.x*r, d.y*r};
float w = k * (mag_d - spring[i].rest_length);
velocity_copy[i].x += w*d_hat.x;
velocity_copy[i].y += w*d_hat.y;

虽然还没有完全优化,但已经变得更加稳定了。

编辑

我也注意到你正在缩放剩余长度(将其除以100)。请不要这样做。

另一个编辑

将此更改为:

    }
    soft_body=soft_body_copy;
    velocity=velocity_copy;
}

转换为:

    }
}
soft_body = move(soft_body_copy);
velocity = move(velocity_copy);

在计算力时,您改变了顶点位置。


2
使用它们之间的距离。您需要一个单独的弹簧结构集合;每个弹簧结构存储其端点的索引和长度。当更新主体时,最简单的方法是为每个现有点添加一个新弹簧,所有弹簧都连接到新点(首先插入新点以获取其索引,然后构造新弹簧)。 - defube
1
unordered_map<pair<int,int>,spring_type> springmap; 然后 spring_type& spring = springmap[{i,j}]; - Mooing Duck
1
@AvZ 在抽象的模型空间中进行模拟(在添加点之前将地图光标映射到模型空间),或者在存储长度之前对其进行缩放。关键是要尽可能保持代码简单(不让屏幕分辨率的细节悄悄地渗入模拟代码中)。 - defube
2
@AvZ 在编写模拟代码时不必担心单位或比例。屏幕空间坐标可以使用单独的转换进行缩放。 - defube
1
我想要为“先计算所有力,然后再更新速度和位置”的原则加入强有力的支持。你甚至可以将一些Verlet或Leapfrog机制融入进来。关键在于,力依赖于位置。在仍在计算力时更新某些位置会引入不物理的偏差,这是导致观察到漂移现象的评估顺序问题。 - Lutz Lehmann
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接