《代码本色:用编程模拟自然系统》习作——第0章:随机行为

前言

近日拜读了Daniel Shiffman先生的著作——《代码本色:用编程模拟自然系统》,决定做一组习作,来对书中提到的随机行为及牛顿运动学进行理解,并对一些实例进行拓展学习,从而提升自己相关方面的知识水平和实践能力。

《代码本色》第0章概述

在第0章中,作者向我们介绍了一系列的随机数算法,以及面向对象编程思想以及面向对象编程思想的应用——建立了一个随机游走类Walker,并进行了很多实践。
随后,作者向我们介绍了Perlin噪声——这一噪声在各领域都有广泛的应用。随后,进行了一维和二维Perlin噪声的实现。

在这一章中,我印象比较深的点是如何实现自定义分布的随机数,因为这将非常实用。其次,Perlin噪声的各种应用也令我印象深刻。下面,我将介绍我根据本章内容而创作的Processing编程习作。

习作

让我们先看看创作的成品:


成品

交互:点击鼠标可以添加新点。
在本次创作中,我使用了Perlin噪声来更新每个点的坐标,并利用ArrayList类和mouseClicked()函数进行点的增加。同时,使用了蒙特卡洛算法进行步长的计算。实现过程将在下面详细介绍。

创作过程

利用Perlin噪声进行随机游走

首先介绍一下什么是Perlin噪声:

一个好的随机数生成器能产生互不关联且毫无规律的随机数。跟我们前面看到的一样,一定程度的随机性有利于有机体和生命活动的建模。然而,单独把随机性作为唯一指导原则是不够的,它并不完全符合自然界的特征。有个算法叫“Perlin噪声”,它就将这一点考虑在内了,该算法是以Ken Perlin命名的。20世纪80年代初,Ken Perlin曾参与电影《电子世界争霸战》(Tron)的制作,在此期间他发明了Perlin噪声算法,用于生成纹理特效。1997年,Perlin因此获得了奥斯卡技术成就奖。Perlin噪声算法可用于生成各种自然特效,包括云层、地形和大理石的纹理。
Processing内置了Perlin噪声算法的实现:noise()函数。noise()函数可以有1~3个参
数,分别代表一维、二维和三维的随机数。

一维Perlin噪声

在使用Perlin函数时我们需要注意,noise(x)的参数x表示在x的位置处的随机数值。所以,如果想要得到不同的值,x的取值必须发生变化。
在书中介绍的示例中。作者使用了noise函数分别对一个小球x和y方向的坐标进行了随机。但是由于Perlin函数的取值是平滑的曲线,所以只要x的间隔足够小,小球看上去就是平滑的移动。以下是我参考示例编写的代码:

  void walk() {    
    position.x = map(noise(noff.x),0,1,0,width);
    position.y = map(noise(noff.y),0,1,0,height);   
    noff.add(0.005,0.005,0);
  }

这里要提一下map函数的应用,map函数拥有“映射”的功能,能让一个数从一个取值范围线性地映射到另一个取值范围:


map函数

上面代码中的Map函数就将一个取值范围为(0,1)的随机数映射到了(0,屏幕宽度或屏幕长度)的取值范围上,确保了小球在屏幕范围内移动。这也就是我们之前提到的,如何实现自定义分布的随机数的方法。
下面放出Walker类的代码:

class Walker {
  PVector position;
  PVector noff;

  Walker() {
    position = new PVector(width/2, height/2);
    noff = new PVector(random(1000),random(1000));
  }

  void display() {
    strokeWeight(2);
    fill(255);
    stroke(0);
    ellipse(position.x, position.y, 10, 10);
  }

  void walk() {    
    position.x = map(noise(noff.x),0,1,0,width);
    position.y = map(noise(noff.y),0,1,0,height);   
    noff.add(0.005,0.005,0);
  }
}

可以看到,我将walk()函数里的步长缩小到了0.005,这样一来就可以实现更平滑的移动。

多个点的生成——ArrayList类的使用

在作者提供的实例中,仅仅定义了一个Walker类的对象w,然后对他进行操作
原代码:

Walker w;

void setup() {
  size(800, 200);
  frameRate(30);

  // Create a walker object
  w = new Walker();
}
void draw() {
  background(255);
  w.walk();
  w.display();
}

如果我想要定义两个或者多个对象,该怎么办呢?Walker w1;Walker w2;Walker w3;…………
这样未免太笨拙了,所以我使用了ArrayList类来定义,存储,访问一系列的Walker对象。让我们首先来看看ArrayList的定义和常用的函数:

An ArrayList stores a variable number of objects. This is similar to making an array of objects, but with an ArrayList, items can be easily added and removed from the ArrayList and it is resized dynamically. This can be very convenient, but it's slower than making an array of objects when using many elements. Note that for resizable lists of integers, floats, and Strings, you can use the Processing classes IntList, FloatList, and StringList.
An ArrayList is a resizable-array implementation of the Java List interface. It has many methods used to control and search its contents. For example, the length of the ArrayList is returned by its size() method, which is an integer value for the total number of elements in the list. An element is added to an ArrayList with the add() method and is deleted with the remove() method. The get() method returns the element at the specified position in the list. (See the above example for context.)
语法:ArrayList<Type>()

事不宜迟,我们就来用一用这个ArrayList,首先,定义一个列表:

ArrayList<Walker> Walkers=new ArrayList<Walker>();

注意!一定要给这个列表开辟内存,不然会产生NullPointerException的错误!!
申请了一个Walker类型的列表后,我们就可以愉快地加东西啦~

  for(int i=0;i<10;i++)
  {
    Walker newWalker=new Walker();
    Walkers.add(newWalker);
  }

用add函数,就是这么方便。
随后,在Draw()函数中定义他们的行为。

void draw() {
  background(0);
  for(int i=0;i<Walkers.size();i++)
  {
    Walkers.get(i).walk();
    Walkers.get(i).display();
  }

这样一来,我们就生成了10个Walker的对象,让我们看看效果:


GIF1.gif

紧接着,为他们连线,这里,我编写了一个二层循环,让每一个点和所有点都进行连线,代码如下:

    for(int i=0;i<Walkers.size();i++)
  {
    stroke(126);
    for(int j=0;j<Walkers.size();j++)
    {
      line(Walkers.get(i).position.x,Walkers.get(i).position.y,Walkers.get(j).position.x,Walkers.get(j).position.y);
    }
  }

让我们看看效果:


GIF2.gif

效果非常不错。
下一步,我添加了鼠标交互添加新点的功能,使用了void mouseClicked() 函数,代码如下:

void mouseClicked() 
{
    Walker newWalker=new Walker();
    Walkers.add(newWalker);
}

非常简单,但有效:


GIF3.gif

蒙特卡洛随机数的使用

完成了上面的工作之后,我对目前作品的感受是,缺乏了点灵动性。
究其原因,是因为我的步长一直不变,这些点有些缺乏“生机”,那么该如何让这些点的移动看起来更有灵动性呢?我决定让我们的步长不再固定,为此,我使用了蒙特卡洛随机数。
让我们先来看看什么是蒙特卡洛随机数。

生成两个随机数,而不是只生成一个随机数。第一个随机数只是
一个普通的随机数。第二个随机数我们称作“资格随机数”,用来决定第一个随机数的取
舍。那些资格更高的随机数被选中的概率更大,而资格更低的随机数被选中的概率更小。
下面是计算步骤(只考虑位于0~1的随机数):
1.选择一个随机数R1;
2.计算R1被选中的资格概率P,假设P = R1;
3.选择另一个随机数R2;
4.如果R2小于P,那么R1就是我们要的随机数;
5.如果R2大于P,回到第(1)步重新开始。
在本例中,一个随机数被选中的资格概率的大小等于其本身。假如我们选中的R1是0.1,
这意味着R1被最后选中的概率是10%。如果R1是0.83,那么它有83%的概率被最后选中。
数字越大,最后被选择的概率也越大。
该算法称为蒙特卡洛算法,以蒙特卡洛大赌场命名


蒙特卡洛

我对该算法进行了改写,让r1和r2的随机数范围可以自定义,并将其写入了Walker类,代码如下:

  float montecarlo(float a ,float b){
    while (true)
    {
      float r1 = random(a);
      float probability = r1; 
      float r2 = random(b); 
      if (r2 < probability)
       { 
       return r1;
       }
    }
  }

随后,将之前固定的步长修改成调用这个函数:

  void walk() {    
    position.x = map(noise(noff.x),0,1,0,width);
    position.y = map(noise(noff.y),0,1,0,height);   
    noff.add(montecarlo(0.01,0.02),montecarlo(0.01,0.02),0);
  }

可以看到,这样一来,步长就变成了利用蒙特卡洛算法生成的随机数,增加了随机性,让我们来看看效果:


GIF4.gif

很明显,点的移动更加具有灵动性了。

下面,附上所有代码:
Walker类:

// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com

// A random walker class!

class Walker {
  PVector position;
  PVector noff;

  Walker() {
    position = new PVector(width/2, height/2);
    noff = new PVector(random(1000),random(1000));
  }

  void display() {
    strokeWeight(2);
    fill(255);
    stroke(0);
    ellipse(position.x, position.y, 10, 10);
  }

  // Randomly move up, down, left, right, or stay in one place
  void walk() {    
    position.x = map(noise(noff.x),0,1,0,width);
    position.y = map(noise(noff.y),0,1,0,height);   
    noff.add(montecarlo(0.01,0.02),montecarlo(0.01,0.02),0);
  }
  
  float montecarlo(float a ,float b){
    while (true)
    {
      float r1 = random(a);
      float probability = r1; 
      float r2 = random(b); 
      if (r2 < probability)
       { 
       return r1;
       }
    }
  }
}

主函数:

ArrayList<Walker> Walkers=new ArrayList<Walker>();
void setup() {
  size(800, 800);
  frameRate(30);

  // Create a walker object
  for(int i=0;i<3;i++)
  {
    Walker newWalker=new Walker();
    Walkers.add(newWalker);
  }

}

void draw() {
  background(0);
  // Run the walker object
  for(int i=0;i<Walkers.size();i++)
  {
    Walkers.get(i).walk();
    Walkers.get(i).display();
  }
    for(int i=0;i<Walkers.size();i++)
  {
    stroke(126);
    for(int j=0;j<Walkers.size();j++)
    {
      line(Walkers.get(i).position.x,Walkers.get(i).position.y,Walkers.get(j).position.x,Walkers.get(j).position.y);
    }
  }
}

void mouseClicked() 
{
    Walker newWalker=new Walker();
    Walkers.add(newWalker);
}

总结

本文介绍了《代码本色:用编程模拟自然系统》第0章的主要内容,并在示例的基础上进行了拓展性的创作。

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

推荐阅读更多精彩内容

  • 写在前面 组件化是近年来比较火的一个概念,现在很多公司的 app 包含的内容和模块越来越多,代码的维护和迭代也会产...
    软件iOS开发阅读 340评论 0 1
  • 今天 还是状态情绪调整不过来,太容易失落了。还是要告诉自己,别人的拒绝是很平常的一件事,不要因为别人的拒绝影响自己...
    光年Fineyoga阅读 135评论 0 0
  • 2017年4月11日,有幸能够参与“龙岗区教育系统成都副校长班与第七期青干班专题培训”的服务工作,虽忙碌却收获颇多...
    严过留痕295阅读 652评论 0 0
  • 昨晚10点半就睡觉了 还是9点多才起床 做饭洗衣服 看小说 动漫《声之形》 睡觉 做饭 看网课 写笔记 聊天 明天...
    逆风追梦人阅读 138评论 0 0
  • 上个世纪60年代,美国心理学教授米歇尔对斯坦福必应幼儿园的学龄前儿童做了一个实验:用简单的困境挑战他们,给孩子们二...
    安雯阅读 458评论 0 3