《代码本色:用编程模拟自然系统》习作——第4章:粒子系统

前言

近日拜读了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));  
    }
  }

事实上,即使不定义这个类,也可以轻松实现对应的功能,这样做是为了让代码更简洁,有序,符合面向对象编程的思想。


ArrayList的利用

在实现了基本的功能之后,我对粒子系统进行了艺术的加工,先来讲讲烟花系统。烟花中每个粒子的加速度和初始方向都是随机数,经过调试,这个参数产生的效果最佳:

    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章的主要内容,并在示例的基础上进行了拓展性的创作,创作了两个粒子系统。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352