前言
近日拜读了Daniel Shiffman先生的著作——《代码本色:用编程模拟自然系统》,决定做一组习作,来对书中提到的随机行为及牛顿运动学进行理解,并对一些实例进行拓展学习,从而提升自己相关方面的知识水平和实践能力。
《代码本色》第4章概述
在第4章中,作者向我们介绍了Processing中一个最经典也是最吸引人的话题——粒子系统。
本章了讨论粒子系统的实现策略。探讨了以下问题:在实现粒子系统时,如何组织
代码;如何存放单个粒子及整个系统的相关信息。
什么是粒子系统?
粒子系统就是一系列独立对象的集合,这些对象通常用简单的图形或者点来表示。为
什么我们要学习粒子系统呢?毫无疑问,粒子系统可以用于模拟各种自然现象(比如
爆炸)。实际上,它的作用不局限于此。如果我们要用代码对自然界的各种事物建
模,要接触的系统肯定并不是由单个物体组成的,系统内部会有很多物体,而粒子系
统非常适合对复数系统进行建模。比如一堆弹球的弹跳运动、鸟群的繁殖,以及生态
系统的演化,这些研究对象都是由复数组成的系统。
此外,在本章中作者还介绍了面向对象编程的两个重要思想:继承和多态。这两个概念非常重要,虽然之前学过,但是阅读这本书很好的让我回顾了这两个知识点。
继承:
通过继承,类可以从其他类中继承属性(变量)和功能(方法)。子类自动从父类中继承所有变量和函数,除此之外,子类还可以有父类没有的函数和变量。继承关系符合树形结构,就像是一棵不断演化的“生命之树”。
继承的两个关键字:
extends 该关键词指出当前类的父类。注意,类只能直接继承一个父类,尽管
如此,类的父类可以继承其他类。举个例子,狗继承自动物,梗犬则继承自狗。
继承关系会自下而上一直延续下去。
super() 它会调用父类的构造函数。换句话说,你在父类的构造函数中做了什
么,子类的构造函数也会做同样的事情。除了调用super()函数,你还可以在子
类的构造函数中进行子类专有的初始化操作。如果父类的构造函数带有参
数,super()函数也带有相同的参数。
多态:
多态按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
下面,我将介绍我根据本章内容而创作的Processing编程习作。
习作
本章中,我创作了两个作品,因为对粒子系统非常感兴趣,它所给予我的艺术美感令我十分神往。
作品1:鼠标交互式烟花生成器:
作品2:鼠标交互式粒子喷射器:
第二个作品点击鼠标可以更换配色。
创作过程
首先是单个粒子的粒子类编写,这个部分相对比较简单,但也很关键。正如前几章中的方法那样,首先定义三个PVector向量变量:
PVector position;
PVector velocity;
PVector acceleration;
这个在前几章中已经介绍过很多次了,就不再复述。
随后,添加update()方法和display()方法,用来更新位置和绘制粒子。和之前不同的是,粒子类还要添加一个isDead()方法,用来判断粒子是否消亡,让粒子在经过一段时间后消亡,可以产生更好的效果。
接着,我们来介绍粒子系统类——ParticleSystem类。
在粒子类中,我们实现了对粒子初始位置的设置,以及定义粒子的初速度和加速度。而在这个ParticleSystem类中,我们只需要生成一系列的粒子,把他们加入一个ArrayList就可以了。举个例子,这是我烟花项目中ParticleSystem类的初始化过程:
ParticleSystem(int num, PVector v) {
particles = new ArrayList<Particle>();
origin = v.get();
for (int i = 0; i < num; i++) {
particles.add(new Particle(origin));
}
}
事实上,即使不定义这个类,也可以轻松实现对应的功能,这样做是为了让代码更简洁,有序,符合面向对象编程的思想。
在实现了基本的功能之后,我对粒子系统进行了艺术的加工,先来讲讲烟花系统。烟花中每个粒子的加速度和初始方向都是随机数,经过调试,这个参数产生的效果最佳:
acceleration = new PVector(0,0.05);
velocity = new PVector(random(-3,3),random(-3,3));
此外,每次生成的粒子数我设定为500个,如果过多会显得过于方正规则,过少则会显得过于稀疏。
然后是粒子留下的线条。众所周知,我们的眼睛拥有视觉停留的能力。所以烟花也要模拟出这个效果。实现方法很简单,在每次刷屏的时候,用透明度不高的矩形来替代全屏刷屏:
fill(0,0,0,10);
rect(0,0,width,height);
其中,fill的第4个参数就是透明度alpha。
对于第二个作品粒子发射器,为了让粒子的活动更具有灵活性和随机性,我利用了噪声让它们的运行更加随机。
n = noise(position.x / noiseScaleX, position.y / noiseScaleY);
angle = map(n, 0,8, 0, 360);
velocity = new PVector(cos(angle*ran), sin(angle*ran));
velocity.add(acceleration);
velocity.add(acceleration);
position.add(velocity);
对于Perlin噪声的使用,前面已经提过很多次了,这里便不再复述。
最后,就是颜色的变化。颜色的变化我是通过随机数和参数传递的方式实现,其中随机数也很有讲究。比如在第二个作品中,我选择了四种配色方案,为了保证颜色的美感,在设置随机颜色的时候,需要进行限制:
switch(ColorTheme%4) {
case 0:
col = color(random(50, 200), 255, random(100, 200));
break;
case 1:
col = color(random(10, 255), random(20, 200), 255);
break;
case 2:
col = color(255, random(20,150), random(20,255));
break;
default:
col = color(100, random(20, 255), 255);
break;
}
可以看到,对rgb三个通道进行不同程度的约束,就可以实现效果较好的配色方法。然后添加鼠标点击事件:
void mouseClicked()
{
ColorTheme++;
//println(ColorTheme);
}
令ColorTheme变量不断累加,然后计算ColorTheme变量除4的余数,来选择当前的配色方案。
最后,贴上所有代码:
作品1:鼠标交互式烟花生成器:
Particle类:
class Particle {
PVector position;
PVector velocity;
PVector acceleration;
float lifespan;
color FireworkColor;
Particle(PVector l) {
acceleration = new PVector(0,0.05);
velocity = new PVector(random(-3,3),random(-3,3));
position = l.get();
lifespan = 255;
}
void run(color newcolor) {
FireworkColor=newcolor;
update();
display();
}
void update() {
velocity.add(acceleration);
position.add(velocity);
lifespan -= 2.0;
}
void display() {
noStroke();
fill(FireworkColor,lifespan);
ellipse(position.x,position.y,2,2);
}
boolean isDead() {
if (lifespan < 0.0) {
return true;
} else {
return false;
}
}
}
ParticleSystem类:
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));
}
}
void run(color newcolor) {
for (int i = particles.size()-1; i >= 0; i--) {
Particle p = particles.get(i);
p.run(newcolor);
if (p.isDead()) {
particles.remove(i);
}
}
}
void addParticle() {
particles.add(new Particle(origin));
}
boolean dead() {
if (particles.isEmpty()) {
return true;
}
else {
return false;
}
}
}
主函数:
ArrayList<ParticleSystem> systems;
int particleCount=0;
color FireWork=color(0,255,255);
void setup() {
size(800,800);
systems = new ArrayList<ParticleSystem>();
}
void draw() {
fill(0,0,0,10);
rect(0,0,width,height);
for (ParticleSystem ps: systems) {
ps.run(FireWork);
if(particleCount<1)
{
ps.addParticle();
}
}
particleCount++;
}
void mousePressed() {
FireWork=color(int(random(100,255)),int(random(100,255)),int(random(100,255)));
particleCount=0;
systems.add(new ParticleSystem(500,new PVector(mouseX,mouseY)));
if(systems.size()>1)
{
systems.remove(systems.get(0));
}
}
作品2:鼠标交互式粒子喷射器:
Particle类:
class Particle {
PVector position, velocity, acceleration;
color col;
int offset_ = 200;
float life;
float noiseScaleX ;
float noiseScaleY ;
float noiseScaleZ ;
float n;
float angle;
public Particle() {
noiseScaleX = 650;
noiseScaleY = 650;
life = 200;
position = new PVector(mouseX, mouseY);
velocity = new PVector(random(-3,3), random(-3,3));
acceleration = new PVector(random(-1.5,1.5), random(-1.5,1.5));
}
void update2(float ran) {
n = noise(position.x / noiseScaleX, position.y / noiseScaleY);
angle = map(n, 0,8, 0, 360);
velocity = new PVector(cos(angle*ran), sin(angle*ran));
velocity.add(acceleration);
velocity.add(acceleration);
position.add(velocity);
life -= 1;
}
void display(int ColorTheme) {
switch(ColorTheme%4) {
case 0:
col = color(random(50, 200), 255, random(100, 200));
break;
case 1:
col = color(random(10, 255), random(20, 200), 255);
break;
case 2:
col = color(255, random(20,150), random(20,255));
break;
default:
col = color(100, random(20, 255), 255);
break;
}
stroke(col);
strokeWeight(2);
point(position.x, position.y);
}
Boolean isDead(){
if(life<=0){
return true;
}else{
return false;
}
}
}
ParticleSystem类:
class ParticleSystem{
int Particletheme=0;
ArrayList<Particle> p ;
ParticleSystem(){
p = new ArrayList<Particle>();
}
void run(int ColorTheme){
for(int i = 0 ; i<p.size() ; i++){
float ran = noise(100);
p.get(i).update2(ran);
p.get(i).display(ColorTheme);
if(p.get(i).isDead()){
p.remove(i);
}
}
}
void addParticle()
{
for(int i=0;i<5;i++)
{
p.add(new Particle());
}
}
}
主函数:
ParticleSystem ps ;
int ColorTheme=0;
void setup(){
ps = new ParticleSystem();
size(800, 800);
}
void draw(){
noStroke();
fill(0,10);
rect(0, 0, width , height);
ps.addParticle();
ps.addParticle();
ps.run(ColorTheme);
}
void mouseClicked()
{
ColorTheme++;
//println(ColorTheme);
}
总结
本文介绍了《代码本色:用编程模拟自然系统》第4章的主要内容,并在示例的基础上进行了拓展性的创作,创作了两个粒子系统。