TheNatureOfCode读书笔记01

前言

最近在看《The Nature of Code - Simulating Natural Systems with Processing》这本书,作者是 Daniel Shiffman(这本书在作者的网站上有电子版。),笔锋幽默,讲解十分犀利,看到此书相见恨晚。一直觉得要做一个笔记系列,记录下来阅读这本书的一些笔记以及心得体会。所以便有了这篇文章。

关于这本书,我想先聊聊下面几点:

作者在书中用的语言是 Processing。关于这个语言,我想先引用百度百科里的介绍来说下,如果没听过或者不了解的读者朋友可以有个大概的了解。

Processing是一种具有革命前瞻性的新兴计算机语言,它的概念是在电子艺术的环境下介绍程序语言,并将电子艺术的概念介绍给程序设计师。它是 Java 语言的延伸,并支持许多现有的 Java 语言架构,不过在语法 (syntax) 上简易许多,并具有许多贴心及人性化的设计。Processing 可以在 Windows、MAC OS X、MAC OS 9 、Linux 等操作系统上使用。目前最新版本为2.2.1。以 Processing 完成的作品可在个人本机端作用,或以Java Applets 的模式外输至网络上发布。

就像吴军在《数学之美》中写道:

技术分为术和道两种,具体的做事方法是术,做事的原理和原则是道。这本书(指的的是数学之美)的目的是讲道而不是术。很多具体的搜索技术(作者是在第8章《简单之美-布尔代数和搜索引擎的索引》中提及术和道)很快会从独门绝技到普及,再到落伍,追求术的人一辈子工作很辛苦。只有掌握了搜索的本职和精髓才能永远游刃有余。

这里我想说的是,语言并不重要。关键是代码背后那些思路想法,简单也好,复杂也罢,解决方法也有很多种。只有当我们了解到背后的原理机制,才算是真正的得道。所以不要局限于语言,当了解原理后,可以很容易用自己最熟悉或者最喜爱的语言移植过去,不同的只是语法、语言的API以及语言API带来的一些算法上的些微不同。

这个系列的读书笔记读者如果愿意看下去,在阅读代码或者阅读文章时,要时刻和我一样,去体会背后的原理。原理有些简单,但不要嗤之以鼻,复杂由简单而来,复杂也可以拆分成简单,就看如何拆分,总之,想法很重要。

  • 这本书国内有译本了,不想看英文的话,可以考虑入手中文版。淘宝京东当当亚马逊应该都有卖。

  • 这本书作者让我们知道了如何通过软件来捕捉自然界难以琢磨的演进和突变,书中涉及到了力、三角、分形、细胞自动机、遗传算法等等,这些知识点在建模、动画、游戏、人工智能等领域都有着广泛的应用。不管读者朋友来自于程序的哪个领域,我相信都会有不小的收获。

  • 这个读书笔记系列更多的是梳理思路,整理知识点,原汁原味请阅读作者原版书籍,我也推荐你这么做。

废话了这么多,开始我们的第一篇吧—-夜空。

夜空

目的

用代码来模拟夜空,感觉到了即可。

思路

  • 夜空中都有哪些元素呢?天空?星星?划破天际的流星?
  • 天空的感觉
  • 星星闪烁的感觉
  • 流星坠落的感觉

逐个击破-天空

天空这里我们用一个渐变色来”美化“造成一种深邃的感觉。渐变色的两头颜色比较重要,直接影响着夜空的像不像。

天空类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Sky
{

int Y_AXIS = 1;
int X_AXIS = 2;
private color c1;
private color c2;
private float skyW;
private float skyH;
Sky(float skyWidth, float skyHeight) {
this.skyW = skyWidth;
this.skyH = skyHeight;
this.c1 = color(8, 40, 90);
this.c2 = color(190, 220, 250);
}

void display() {
setGradient(0, 0, skyW, skyH, c1, c2, Y_AXIS); //夜空背景色为纵向渐变色,渐变色两端颜色为c1和c2
}

void setGradient(int x, int y, float w, float h, color c1, color c2, int axis ) {
noFill();
if (axis == Y_AXIS) { // Top to bottom gradient
for (int i = y; i <= y+h; i++) {
float inter = map(i, y, y+h, 0, 1);
color c = lerpColor(c1, c2, inter);
stroke(c);
line(x, i, x+w, i);
}
}
else if (axis == X_AXIS) { // Left to right gradient
for (int i = x; i <= x+w; i++) {
float inter = map(i, x, x+w, 0, 1);
color c = lerpColor(c1, c2, inter);
stroke(c);
line(i, y, i, y+h);
}
}
}
}

逐个击破-星星

在 Processing 中,我们用一个很小的白色圆形就可以很好的表现星星,但仅此还不够。”一闪一闪亮晶晶,满天都是小星星“,给予星星闪烁的特质,才会更贴切人们的预期。

在数学表示中,什么表现闪烁非常合适?尽情的开动你的脑筋吧。有的人说用线性表示,随着时间慢慢变大,当圆的直径大于我们规定的最大值,就开始慢慢减小,当减小到我们规定的最小值,就又开始慢慢变大。但这种线性的表示会使得星星的闪烁表现的很平淡。

试试正弦函数吧。

设想如果我们的小星星的直径大小按照上图变化,随着横轴时间的推进,直径大小按照正弦曲线来波动,斜率大的地方和斜率小的地方表现出的速率就会不一样,哦不,这里直径不能为负值,我们需要做个绝对值处理。如下图:

星星类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Star
{

PVector pos;
float diameter;
float step;
float MAX_DIAMETER = 6; // 最大直径
float MIN_DIAMETER = 2; // 最小直径
Star(PVector pos) {
this.pos = pos;
this.diameter = random(MIN_DIAMETER, MAX_DIAMETER); // 直径随机 星星大小不一
this.step = random(0, 5);
}

void display() {
step += 0.05;
float d = abs(diameter*sin(step)); //直径正弦变化
noStroke();
fill(255);
ellipse(pos.x, pos.y, d, d);
}


}

逐个击破-流星

流星有着尾巴,这是流星与空气摩擦燃烧造成的。我们看电影电视图画上一直如此。流星的绘制不同语言绘制方法不同。但我们可以用一个统一点的思路来看待。

我们先画一个流星的头部,白色圆形就可以了,然后绘制流星的尾巴。流星的尾巴由一连串圆来绘制,从头到尾,圆直径愈来愈小,透明度愈来愈高,以此思路便可作出一个简易的流星的感觉。

流星类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Meteor
{

float headDiameter; //头部直径
float tailLength; //尾巴长度

float tailWidth; //尾巴宽度
float lifeTime; //生命周期

PVector pos; // 速度
PVector speed; //位置
Meteor() {
reset();
}

void update() {
pos.add(speed);

lifeTime -= 0.35;

if (isDead()) {
reset(); //死亡后重置
}
}

void display() {
noStroke();
fill(255);
ellipse(pos.x, pos.y, headDiameter, headDiameter);

translate(pos.x, pos.y);

for (int i = 0; i < tailLength; i++) {
float alpha = map(i, 0, tailLength, 100, 0);
fill(255, alpha);
tailWidth = map(i, 0, tailLength, headDiameter * 0.6, 0);
ellipse(i, -i * abs(speed.y/speed.x), tailWidth, tailWidth);
}
}

void reset() {
this.headDiameter = random(6, 8);
this.pos = new PVector(width/2 + random(width/2), -random(200)); //流星的起始位置
this.tailLength = random(20)+150;
this.lifeTime = random(20)+50;

float speedX = -(random(1) + 8);
float speedY = -speedX;
this.speed = new PVector(speedX, speedY); //速度向量,决定流星的坠落方向
}


boolean isDead() {
return lifeTime < 0;
}
}

美丽的夜空,组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Sky sky;

ArrayList<Meteor> meteorsArr;
ArrayList<Star> starsArr;
int STARS_NUM = 12; //星星的数目
int METEORS_NUM = 3; //流星的数目(流星有生命周期,死亡后重新设置位置等属性,会反复重用)
void setup() {
size(800, 500);

starsArr = new ArrayList<Star>();
meteorsArr = new ArrayList<Meteor>();
sky = new Sky(width, height);

for (int i = 0; i < STARS_NUM; i++) {
Star star = new Star(new PVector(random(width), random(height)));
starsArr.add(star);
}

for (int i = 0; i < METEORS_NUM; i++) {
Meteor meteor = new Meteor();
meteorsArr.add(meteor);
}
}

void draw() {
sky.display();
for (int i = 0; i < starsArr.size(); i++) {
Star star = starsArr.get(i);
star.display();
}

for (int i = meteorsArr.size()-1; i >= 0; i--) {
Meteor meteor = meteorsArr.get(i);
meteor.update();
meteor.display();
}
}

Github代码

以上都是我们刚才提到的”道“,基本解释了一个夜空中的元素中需要考虑的一些要点。夜空中的”道“比较简单,喂,这位同学,你能用 Objective-C 在 ios 里试试吗?

坚持原创技术分享,您的支持将鼓励我继续创作!