书名:代码本色:用编程模拟自然系统
作者:Daniel Shiffman
译者:周晗彬
ISBN:978-7-115-36947-5
第6章目录
6.11 群/体行为(不要碰到对方)
1、ArrayList
- 在粒子系统类中,我们用ArrayList存放粒子的列表。我们会在本例中做同样的事情:把一组Vehicle对象存放到ArrayList中。
ArrayList<Vehicle> vehicles; 声明由小车对象组成的ArrayList
void setup() {
vehicles = new ArrayList<Vehicle>; 用一系列小车对象填充ArrayList
for (int i = 0; i < 100; i++) {
vehicles.add(new Vehicle(random(width),random(height)));
}
}
2、draw()函数
- 如果要在draw()函数中处理所有小车对象,只需遍历这个ArrayList,并在对象上调用相应的方法。
void draw(){
for (Vehicle v : vehicles) {
v.update();
v.display();
}
}
3、添加行为
- 我们要为小车添加一种行为。比如让小车寻找鼠标所在的目标位置:
v.seek(mouseX, mouseY);
- 但这只是个体的行为,前面我们一直在研究个体的行为,现在要研究群体行为。让我们从分离(separate)行为开始。分离行为等同于以下命令:“请不要和你的邻居发生碰撞!”
v.separate();
- 这个函数还有些问题,我们还少了一些东西。分离指的是“从其他个体上分开”,其他个体指的是列表中的其他小车。
v.separate(vehicles);
4、群体行为的实现
- 相比于粒子系统,本例有很大不同。在粒子系统中,个体(粒子或小车)单独运作;
但在本例中,我们会告诉个体:“现在轮到你操作了,你需要考虑系统中的每个个体,所以我要向你传入一个ArrayList,里面存放了所有其他个体。” - 为了实现群体行为,我们用以下代码实现setup()函数和draw()函数。
ArrayList<Vehicle> vehicles;
void setup() {
size(320,240);
vehicles = new ArrayList<Vehicle>();
for (int i = 0; i < 100; i++) {
vehicles.add(new Vehicle(random(width),random(height)));
}
}
void draw() {
background(255);
for (Vehicle v : vehicles) {
v.separate(vehicles); 这是本节加入的新东西,小车在计算分离转向力时需要检查其他所有对
v.update();
v.display();
}
}
5、转向力
-
这只是一个开头,真正的操作在separate()函数中实现。我们先思考这个函数的实现方式。Reynolds提到:“用转向避免拥堵”,也就是说,如果某辆小车和你的距离太近,你应该转向以远离它。对此你是否觉得很熟悉?“寻找行为”指的是朝着目标转向,将“寻找行为”的转向力反转,就能得到躲避行为的转向力。
图6-33 -
但如果同时有多辆小车的距离都很近,这时候该怎么做?在这种情况下,我们可以对所有远离小车的向量求平均值,用平均向量计算分离行为的转向力。
图6-34
6、separate()函数的实现
- 下面我们开始实现separate()函数,它的参数是一个ArrayList对象,里面存放了所有小车对象。
- 在这个函数中,我们要遍历所有的小车,检查它们是否过于接近。
float desiredseparation = 20; 这个变量指定最短距离
for (Vehicle other : vehicles) {
float d = PVector.dist(location, other.location); 当前小车和其他小车之间的距离
if ((d > 0) && (d < desiredseparation)) { 如果小车的距离小于20像素,这里的代码就会执行
}
}
注意:在上面的代码中,我们不只检查距离是否小于desiredseparation(过于接近!),还要检查距离是否大于0。这么做是为了确保小车不会意图和自身分离。所有小车对象都在ArrayList中,一不小心你就会让一辆小车与自身发生比较。
- 一旦发现和某辆小车过于接近,我们就应该记录远离这辆小车的向量。
if ((d > 0) && (d < desiredseparation)) {
PVector diff = PVector.sub(location, other.location); 一个指向远离其他小车方向的向量
diff.normalize();
}
- 有了这个diff向量还不够,我们还要针对每辆靠近的小车计算diff向量,再计算它们的平均向量。将所有向量加在一起,再除以总数,就可以得到平均向量!
PVector sum = new PVector(); 从一个空向量开始
int count = 0;
for (Vehicle other : vehicles) { 我们还要记录有多少辆小车的距离过近
float d = PVector.dist(location, other.location);
if ((d > 0) && (d < desiredseparation)) {
PVector diff = PVector.sub(location, other.location);
diff.normalize();
sum.add(diff); 将所有向量加在一起,并递增计数器
count++;
}
}
if (count > 0) { 必须确保至少找到一辆距离过近的小车,然后才执行除法操作(避免除零的情况!)
sum.div(count);
}
- 有了平均向量(sum向量)之后,我们将它延伸至最大速率,就可以得到所需速度——希望小车以最大速率朝着这个方向运动!一旦有了所需速度,我们就可以根据Reynolds的公式计算转向力:
转向力 = 所需速度 - 当前速度。
if (count > 0) {
sum.div(count);
sum.setMag(maxspeed); 延伸至最大速率(使其成为所需速度)
PVector steer = PVector.sub(sum,vel); Reynolds的转向力公式
steer.limit(maxforce);
applyForce(steer); 将力转化为小车的加速度
}
7、示例
示例代码6-7 群集行为:分离
void separate (ArrayList<Vehicle> vehicles) {
float desiredseparation = r*2; 分离的距离取决于小车的尺寸
PVector sum = new PVector();
int count = 0;
for (Vehicle other : vehicles) {
float d = PVector.dist(location, other.location);
if ((d > 0) && (d < desiredseparation)) {
PVector diff = PVector.sub(location, other.location);
diff.normalize();
diff.div(d); 计算小车和其他小车之间的距离:距离越近,分离的幅度越大;距离越远,分离sum.add(diff);
count++;
}
}
if (count > 0) {
sum.div(count);
sum.normalize();
sum.mult(maxspeed);
PVector steer = PVector.sub(sum, vel);
steer.limit(maxforce);
applyForce(steer);
}
}