我在飞机上有300或更少相等半径的光盘。在时间0,每个盘都在一个位置。在时间1,每个盘处于可能不同的位置。我希望为每个光盘生成0到1之间的2D路径,使得光盘不相交并且路径相对有效(短)并且如果可能的话具有低曲率。 (例如,直线优于波浪线)
You can see a demo我最好的尝试(通过Javascript + WebGL)。请注意,由于涉及的计算,它将在旧计算机上缓慢加载。它似乎适用于Windows下的Firefox / Chrome / IE11。
在这个演示中,我将每个光盘代表一个"弹性乐队"在3D中(也就是说,每个光盘在每次都有一个位置)并运行一个简单的游戏式物理引擎来解决约束并将每个时间点视为具有弹簧的质量到上一次/下一次。 ('时间'在这种情况下只是第三个维度。)
这对于小N(< 20)实际上非常有效,但在常见的测试用例中(例如,从以圆圈排列的光盘开始,将每个光盘移动到圆圈上的相反点),这无法生成令人信服的路径因为约束和弹性在整个弹簧中缓慢传播。 (例如,如果我将时间切割成100个离散级别,则弹性带中的张力仅在每个模拟周期中传播一个级别)这使得良好的解决方案需要许多(> 10000)次迭代,并且对于我的应用来说这是非常慢的。它也无法合理地解决许多N> 40个案例,但这可能仅仅是因为我无法进行足够的迭代。
我最初的尝试是一个爬山者,从直线路径开始,逐渐变异。比目前最佳解决方案更好的测量解决方案取代了目前最好的解决方案更好的测量结果来自交叉量(即完全重叠测量比仅仅放牧更糟糕)和路径长度(更短的路径更好)。
这产生了一些令人惊讶的好结果,但不可靠的是,很可能经常陷入局部极小。对于N> 20,它非常慢。我尝试应用一些技术(模拟退火,遗传算法方法等)试图绕过局部最小问题,但我从未取得太大成功。
我正在优化"松紧带"模型使得张力和约束在时间维度上传播得更快。在许多情况下,这将节省大量所需的迭代,但是在高度受限的情况下(例如,许多光盘试图穿过相同的位置)仍然需要无法维持的迭代量。我不是如何解决约束或更快地传播弹簧的专家(我已经尝试过阅读一些关于不可拉伸布料模拟的论文,但我还没有弄清楚它们是否适用),所以我感兴趣的是,如果有一个好方法可以解决这个问题。
答案 0 :(得分:6)
为了好玩而玩了一下这里的结果:
<强>算法:强>
constant*destination_vector
a
v
ang
如果没有找到自由方向标记光盘卡住
这就是圆圈到反圆路径的样子:
这是随机到随机路径的样子:
卡住的光盘黄色(在这些情况下都没有),并且移动光盘已经到达目的地。 如果没有路径,如果光盘已经在目标圈子中,那么这也可能会卡住另一个目的地。为了避免这种情况,您还需要更换碰撞光盘...您可以使用ang,a,v
常数来进行不同的外观,也可以尝试随机的角度旋转方向以避免旋转/扭曲运动
这里是我使用的源代码(C ++):
//---------------------------------------------------------------------------
const int discs =23; // number of discs
const double disc_r=5; // disc radius
const double disc_dd=4.0*disc_r*disc_r;
struct _disc
{
double x,y,vx,vy; // actual position
double x1,y1; // destination
bool _stuck; // is currently stuck?
};
_disc disc[discs]; // discs array
//---------------------------------------------------------------------------
void disc_generate0(double x,double y,double r) // circle position to inverse circle destination
{
int i;
_disc *p;
double a,da;
for (p=disc,a=0,da=2.0*M_PI/double(discs),i=0;i<discs;a+=da,i++,p++)
{
p->x =x+(r*cos(a));
p->y =y+(r*sin(a));
p->x1=x-(r*cos(a));
p->y1=y-(r*sin(a));
p->vx=0.0;
p->vy=0.0;
p->_stuck=false;
}
}
//---------------------------------------------------------------------------
void disc_generate1(double x,double y,double r) // random position to random destination
{
int i,j;
_disc *p,*q;
double a,da;
Randomize();
for (p=disc,a=0,da=2.0*M_PI/double(discs),i=0;i<discs;a+=da,i++,p++)
{
for (j=-1;j<0;)
{
p->x=x+(2.0*Random(r))-r;
p->y=y+(2.0*Random(r))-r;
for (q=disc,j=0;j<discs;j++,q++)
if (i!=j)
if (((q->x-p->x)*(q->x-p->x))+((q->y-p->y)*(q->y-p->y))<disc_dd)
{ j=-1; break; }
}
for (j=-1;j<0;)
{
p->x1=x+(2.0*Random(r))-r;
p->y1=y+(2.0*Random(r))-r;
for (q=disc,j=0;j<discs;j++,q++)
if (i!=j)
if (((q->x1-p->x1)*(q->x1-p->x1))+((q->y1-p->y1)*(q->y1-p->y1))<disc_dd)
{ j=-1; break; }
}
p->vx=0.0;
p->vy=0.0;
p->_stuck=false;
}
}
//---------------------------------------------------------------------------
void disc_iterate(double dt) // iterate positions
{
int i,j,k;
_disc *p,*q;
double v=25.0,a=10.0,x,y;
const double ang=10.0*M_PI/180.0,ca=cos(ang),sa=sin(ang);
const int n=double(2.0*M_PI/ang);
for (p=disc,i=0;i<discs;i++,p++)
{
p->vx=a*(p->x1-p->x); if (p->vx>+v) p->vx=+v; if (p->vx<-v) p->vx=-v;
p->vy=a*(p->y1-p->y); if (p->vy>+v) p->vy=+v; if (p->vy<-v) p->vy=-v;
x=p->x; p->x+=(p->vx*dt);
y=p->y; p->y+=(p->vy*dt);
p->_stuck=false;
for (k=0,q=disc,j=0;j<discs;j++,q++)
if (i!=j)
if (((q->x-p->x)*(q->x-p->x))+((q->y-p->y)*(q->y-p->y))<disc_dd)
{
k++; if (k>=n) { p->x=x; p->y=y; p->_stuck=true; break; }
p->x=+(p->vx*ca)+(p->vy*sa); p->vx=p->x;
p->y=-(p->vx*sa)+(p->vy*ca); p->vy=p->y;
p->x=x+(p->vx*dt);
p->y=y+(p->vy*dt);
j=-1; q=disc-1;
}
}
}
//---------------------------------------------------------------------------
用法很简单:
generate0/1
dt
是以秒为单位的时间)如果您想将其更改为使用t=<0,1>
<0,1>
<强> [注释] 强>
我的测试是实时运行的,但我没有应用<0,1>
范围且没有太多光盘。所以你需要测试它是否足够快你的设置。
要加快速度,您可以:
[edit1]进行一些调整以避免障碍物周围的无限振荡
对于更多光盘,其中一些光盘卡在已经停止的光盘周围弹跳。为避免这种情况,只需偶尔更改ang
步进方向,这就是结果:
你可以在完成之前看到摆动的弹跳
这是改变的来源:
void disc_iterate(double dt) // iterate positions
{
int i,j,k;
static int cnt=0;
_disc *p,*q;
double v=25.0,a=10.0,x,y;
const double ang=10.0*M_PI/180.0,ca=cos(ang),sa=sin(ang);
const int n=double(2.0*M_PI/ang);
// process discs
for (p=disc,i=0;i<discs;i++,p++)
{
// compute and limit speed
p->vx=a*(p->x1-p->x); if (p->vx>+v) p->vx=+v; if (p->vx<-v) p->vx=-v;
p->vy=a*(p->y1-p->y); if (p->vy>+v) p->vy=+v; if (p->vy<-v) p->vy=-v;
// stroe old and compute new position
x=p->x; p->x+=(p->vx*dt);
y=p->y; p->y+=(p->vy*dt);
p->_stuck=false;
// test if coliding
for (k=0,q=disc,j=0;j<discs;j++,q++)
if (i!=j)
if (((q->x-p->x)*(q->x-p->x))+((q->y-p->y)*(q->y-p->y))<disc_dd)
{
k++; if (k>=n) { p->x=x; p->y=y; p->_stuck=true; break; } // if full circle covered? stop
if (int(cnt&128)) // change the rotation direction every 128 iterations
{
// rotate +ang
p->x=+(p->vx*ca)+(p->vy*sa); p->vx=p->x;
p->y=-(p->vx*sa)+(p->vy*ca); p->vy=p->y;
}
else{
//rotate -ang
p->x=+(p->vx*ca)-(p->vy*sa); p->vx=p->x;
p->y=+(p->vx*sa)+(p->vy*ca); p->vy=p->y;
}
// update new position and test from the start again
p->x=x+(p->vx*dt);
p->y=y+(p->vy*dt);
j=-1; q=disc-1;
}
}
cnt++;
}
答案 1 :(得分:4)
这并不完美,但我最好的想法是将光盘移到quadratic Bezier curves。这意味着您每个光盘只有2个自由变量,而您正试图为其找到值。
此时,你可以'#34;插上&#34;错误函数到非线性优化器中。在光盘相互避开方面,你越愿意等待,你的解决方案就会越好。
只有一个实际命中:
不打扰显示匹配,光盘实际上开始重叠:
我已经制作了一个完整的例子,但关键是要最小化的错误函数,我在这里重现:
double errorf(unsigned n, const double *pts, double *grad,
void *data)
{
problem_t *setup = (problem_t *)data;
double error = 0.0;
for(int step=0; step<setup->steps; step++) {
double t = (1.0+step) / (1.0+setup->steps);
for(int i=0; i<setup->N; i++)
quadbezier(&setup->starts[2*i],
&pts[2*i],
&setup->stops[2*i],
t,
&setup->scratch[2*i]);
for(int i=0; i<setup->N; i++)
for(int j=i+1; j<setup->N; j++) {
double d = distance(&setup->scratch[2*i],
&setup->scratch[2*j]);
d /= RADIUS;
error += (1.0/d) * (1.0/d);
}
}
return error / setup->steps;
}
忽略n
,grad
和data
。 setup
描述了优化的具体问题,光盘数量以及启动和停止的位置。 quadbezier
进行贝塞尔曲线插值,将其答案放入->scratch
。我们在路径的一部分检查->steps
点,并测量每个步骤中光盘彼此的接近程度。为了使优化问题更加顺畅,当光盘开始接触时,它没有硬开关,它只是试图让它们尽可能远离彼此。
https://github.com/jkominek/discs
可以使用完全可编译的代码,Makefile和一些用于将一组二次贝塞尔曲线转换为一系列图像的Python大量积分表现有点迟钝,但还有很多改进选择。
n^2
循环并行化在所有点上。t
,然后使用该距离进行错误。确定该次优化的梯度应该更容易。答案 2 :(得分:3)
这种问题的通常解决方案是使用所谓的“热图”(或“影响图”)。对于该字段中的每个点,您计算“热量”值。磁盘向高值移动并远离冷值。热图适用于您的问题类型,因为它们编程非常简单,但可以生成复杂的类似AI的行为。
例如,想象一下只有两个磁盘。如果您的热图规则是等径向的,那么磁盘将仅朝向彼此移动,然后向后移动,来回摆动。如果你的规则在不同的径向上随机化强度,那么行为将是混乱的。您还可以使规则取决于速度,在这种情况下,磁盘在移动时会加速和减速。
通常,说热图规则应该使区域“更热”,它们接近磁盘的最佳距离。太靠近磁盘或太远的地方变得“更冷”。通过更改此最佳距离,您可以确定磁盘聚集的距离。
以下是一些文章,其中的示例代码展示了如何使用热图:
游戏AI Pro,第2卷,热图章节
答案 3 :(得分:0)
我还没有足够的代表发表评论,很抱歉没有回答。 但是对于RTS角度,RTS通常使用A *算法进行路径寻找。您是否有理由坚持使用基于物理的模型?
其次,你的联系尝试相当顺利,但中间加速,表现我最初的想法。由于您的模型将其视为橡皮筋,因此它主要是寻找哪种方式旋转以获得到达所需位置的最短路径。
如果你不担心物理方法,我会尝试如下: 尝试直接朝目标移动。如果碰撞,它应该尝试顺时针绕最近的碰撞滚动,直到它在矢量上与当前位置到目标位置的矢量成90度的位置。
如果我们假设一个盒子顶部的连续5个测试用例和底部连续五个测试用例,它们将直接相互移动直到它们发生碰撞。整个顶行将向右滑动,直到它们向下移动到底行的边缘,因为它向左移动并漂浮在顶行的边缘上方。 (想想威士忌和水杯玻璃技巧在开始时的样子)
由于运动不是由存储在弹簧中的势能确定的,这会在旋转过程中加速物体,因此您可以完全控制模拟过程中速度的变化。
在如上所述的循环测试中,如果所有磁盘都以相同的速度进行初始化,整个团块将转到中间,碰撞并扭转为一个单位大约四分之一圈,此时它们将脱离并且为他们的目标前进。
如果时间是轻微随机的,我认为你会得到你正在寻找的行为。
我希望这会有所帮助。