参考自然代码的前五章模拟随机行为
平台:processing
第五章主要说的是运用processing中封装好的模拟物理系统来模拟一些自然现象。
我们这里实现的是模仿水和风的效果。
三个类:边界类、单个粒子类、粒子群类
在介绍这三个类之前先简要介绍一下封装好的模拟物理包:pbox2d类
PBox2D是一个用于模拟刚体物体运动的物理引擎,所谓的物理引擎,就是将对象相关物理属性的计算封装在类库中,由类库去维护物理属性的变化。
1.世界(world) 管理整个物理模拟过程,它知道坐标空间的所有信息,存放了世界中的所有物体。
2.物体(body) Box2D世界的基础元素,有自己的位置和速度。是否感到似曾相识?在前面力和向量的模拟程序中,我们开发了很多类,Box2D的物体就等同于这些类。
3.形状(shape) 记录所有与碰撞相关的几何信息。注意这个形状是抽象的,在Box2D中对物体的行为进行计算,并不包括渲染图像。4.夹具(fixture) 将形状赋给物体,设置物体的一些属性,比如密度、摩擦系数、复原性。
5.关节(joint) 充当两个物体之间的连接器(或者物体和世界之间的连接器)。
具体用法参见:
https://www.jianshu.com/p/0303377b3d4e
第一个是边界类,边界要实现的效果是当物体碰到边界时会产生碰撞的效果。
成员函数:
float x;
float y;
float w;
float h;
Body b;
Body b就是创建一个body的主题来实现控制。
构造函数:边界就是一个矩形
Boundary(float x_,float y_, float w_, float h_, float a)
{
x = x_;
y = y_;
w = w_;
h = h_;
用PolygonShape来定义多边形:
scalarPixelsToWorld是用来将世界坐标转到像素坐标。
PolygonShape sd = new PolygonShape();
//求出box2d坐标
float box2dW = box2d.scalarPixelsToWorld(w/2);
float box2dH = box2d.scalarPixelsToWorld(h/2);
//设置盒子的大小
sd.setAsBox(box2dW, box2dH);
用bodydef来创建一个物体的身体(在这个封装的类中,物体的内在和形状是分开的)
BodyDef bd = new BodyDef();
bd.type = BodyType.STATIC;
bd.angle = a;
bd.position.set(box2d.coordPixelsToWorld(x,y));
b = box2d.createBody(bd);
我们用夹具把物体的内在和形状连接在一起。
b.createFixture(sd,1);
}
显示函数:画出边界
用getangle函数来获取物体的角度。
void display()
{
noFill();
stroke(0);
strokeWeight(1);
rectMode(CENTER);
float a = b.getAngle();
pushMatrix();
translate(x,y);
rotate(-a);
rect(0,0,w,h);
popMatrix();
}
第二个是单个粒子类
成员变量有两个:
一个是物体,一个是跟踪的向量数组。
Body body;
PVector[] trail;
构造函数:
Particle(float x_, float y_)
{
float x = x_;
float y = y_;
trail = new PVector[6];
for (int i = 0; i < trail.length; i++)
{
trail[i] = new PVector(x, y);
}
用makebody把box添加到box2d世界:
makeBody(new Vec2(x, y), 0.2f);
粒子消失的成员函数:
直接调用destory函数
void killBody()
{
box2d.destroyBody(body);
}
判断粒子是否要消亡,先找到粒子在屏幕上的位置,在判断粒子是否在底部:
boolean done()
{
Vec2 pos = box2d.getBodyPixelCoord(body);
if (pos.y > height+20)
{
killBody();
return true;
}
return false;
}
显示函数,画出粒子
先获取每一个物体在屏幕上的位置,再记录粒子的历史位置放在跟踪数组中。
void display()
{
Vec2 pos = box2d.getBodyPixelCoord(body);
for (int i = 0; i < trail.length-1; i++)
{
trail[i] = trail[i+1];
}
trail[trail.length-1] = new PVector(pos.x, pos.y);
画一个粒子作为轨迹
用beginShape和endShape的组合来画:
beginShape();
noFill();
strokeWeight(2);
stroke(0, 150);
for (int i = 0; i < trail.length; i++)
{
vertex(trail[i].x, trail[i].y);
}
endShape();
}
makebody函数
将box映射到box2d世界,先定义和创造物体
void makeBody(Vec2 center, float r)
{
BodyDef bd = new BodyDef();
bd.type = BodyType.DYNAMIC;
bd.position.set(box2d.coordPixelsToWorld(center));
body = box2d.createBody(bd);
给一个初始的随机速度:
body.setLinearVelocity(new Vec2(random(-1, 1), random(-1, 1)));
把物体的形状定义为圆形:
CircleShape cs = new CircleShape();
cs.m_radius = box2d.scalarPixelsToWorld(r);
FixtureDef fd = new FixtureDef();
fd.shape = cs;
在设置一下摩擦力等参数:
fd.density = 1;
fd.friction = 0; // 湿路滑!
fd.restitution = 0.5;
最后还是用夹具连接:
body.createFixture(fd);
第三个就是粒子系统类
成员变量有两个,分别是储存粒子的数组,和粒子群的起点。
class ParticleSystem
{
ArrayList<Particle> particles;
PVector origin;
构造函数:
ParticleSystem(int num, PVector v)
{
particles = new ArrayList<Particle>(); // 初始化
origin = v.get(); // 存储原点
for (int i = 0; i < num; i++)
{
particles.add(new Particle(origin.x,origin.y)); // 向ArrayList中添加“num”数量的粒子
}
}
运行函数:
显示粒子群,循环调用单个粒子的显示函数
void run()
{
//显示所有的粒子
for (Particle p: particles)
{
p.display();
}
粒子离开屏幕时从box2d世界的列表中删除
for (int i = particles.size()-1; i >= 0; i--)
{
Particle p = particles.get(i);
if (p.done())
{
particles.remove(i);
}
}
}
添加粒子系统函数:
也是循环调用
void addParticles(int n)
{
for (int i = 0; i < n; i++)
{
particles.add(new Particle(origin.x,origin.y));
}
}
还需要加一个判断粒子系统里是否还有粒子的函数:
boolean dead()
{
if (particles.isEmpty())
{
return true;
}
else
{
return false;
}
}
最后是主函数
运用pbox2d的模拟类要先导入包:
import pbox2d.*;
import org.jbox2d.collision.shapes.*;
import org.jbox2d.common.*;
import org.jbox2d.dynamics.*;
创建一个box2d世界的引用
PBox2D box2d;
创建一个边缘类实例的列表
ArrayList<Boundary> boundaries;
创建一个粒子系统类的列表
ArrayList<ParticleSystem> systems;
在setup函数中对上述的对象进行初始化
void setup()
{
size(400,300);
box2d = new PBox2D(this);
box2d.createWorld();
systems = new ArrayList<ParticleSystem>();
boundaries = new ArrayList<Boundary>();
boundaries.add(new Boundary(50,100,300,5,-0.3));
boundaries.add(new Boundary(250,175,300,5,0.5));
}
因为是模仿水的效果,水也会收到重力的影响,所以还要添加一个持续的“重力”
box2d.setGravity(0, -20);
draw函数
!!!!运用此模拟物理系统,必须要调用step函数!!!!
要不然画面不会动。。。
box2d.step();
运行所有的粒子系统
for (ParticleSystem system: systems)
{
system.run();
int n = (int) random(0,2);
system.addParticles(n);
}
显示所有的边界:
// 显示所有的边界
for (Boundary wall: boundaries)
{
wall.display();
}
添加鼠标点击事件
每点一次就添加一个粒子系统
void mouseClicked()
{
systems.add(new ParticleSystem(0, new PVector(mouseX,mouseY)));
}
下面给粒子系统加上风吹的效果
修改单个粒子类
添加一个加力函数:
void applyForce(Vec2 force)
{
Vec2 pos = body.getWorldCenter();
body.applyForce(force, pos);
}
在粒子群类中调用:
void blow()
{
Vec2 wind;
wind = new Vec2(0,0);
for (int i = particles.size()-1; i >= 0; i--)
{
Particle p = particles.get(i);
wind = new Vec2(0.025,-0.025);
p.applyForce(wind);
}
}
还要在draw函数中调用:
鼠标按压函数
if (mousePressed)
{
for (ParticleSystem system: systems)
{
system.blow();
}
}
但是现在风都往一个方向吹,选择改为鼠标在哪里就往鼠标的反方向吹
在粒子群类中修改:
void blow()
{
Vec2 wind;
wind = new Vec2(0,0);
for (int i = particles.size()-1; i >= 0; i--)
{
Particle p = particles.get(i);
if(mouseX<(width/2) && mouseY<(height/2))
{
wind = new Vec2(0.025,-0.025);
}
else if(mouseX>(width/2) && mouseY<(height/2))
{
wind = new Vec2(-0.025,-0.025);
}
else if(mouseX<(width/2) && mouseY>(height/2))
{
wind = new Vec2(0.025,0.025);
}
else if(mouseX>(width/2) && mouseY>(height/2))
{
wind = new Vec2(-0.025,0.025);
}
p.applyForce(wind);
}
}
演示: