郭锐文(@RavenKwok)是来自中国的视觉和程序设计师,擅长充满想象力的生成、算法作品和投影艺术。今天我们将分析他用Processing写了一个名叫“p0629a_2013_worms”的作品,效果如下:

 

是的,如大家所见,在郭老师的作品中,大家拖动鼠标就可以产生一条新的worm,或者将已经存在的worms切割成很多段,同时这些worms可以弯曲收缩、相互吸引和排斥。

 

郭老师的艺术创作和学术研究领域集中于探索计算机程序算法在视觉美学表达上的可能性,他的作品不仅创意十足,而且代码写的也非常整洁。那么接下来我们就去看看郭老师的代码,看看他代码中的worms到底有哪些神奇的地方。

 

心急的同学可以和往常一样,先去[郭老师的OpenProcessing](https://www.openprocessing.org/sketch/102905)上看看在线的效果。

 

· 力

 

在讲解大佬Jason Labbe的作品“FireBrush”的时候,给大家介绍过描述一个粒子的运动可以从速度、加速度、位置这三个角度出发,但大家有没有想过是什么造成了粒子加速度的变化?

 

对,是“力”!

 

相信大家还记得初中时候物理课上学的牛顿第二定理:物体加速度的大小跟作用力成正比,跟物体的质量成反比,且与物体质量的倒数成正比;加速度的方向跟作用力的方向相同。

 

也就是说物体任意时刻的加速度由它当前时刻受到的力决定,并且满足F=ma,其中F是物体当前时刻所收到的力,m是物体的质量,a是物体的加速度。

 

因为郭老师的作品中有很多地方涉及到力的使用,首先我们就来看看如何用Processing来给物体施加一个力。这里得说明一下,这里模拟力的方式和后面郭老师代码中模拟力的形式上略有不同,但是本质上是一样的。

 

下面是一个简单的粒子类,如果对这类不熟悉的同学可以去看看之前代码检测“冰与火之歌”的内容。

 

 

接下来我们给它添加一个成员函数applyForce来给其施加力的作用,我们首先根据公式a=F/m计算出这个力带来的物体的加速度的改变,然后对该粒子的加速度进行更新。

 

 

下面我们来给这个物体施加引力。引力对我们来说可能最常见了。说起引力,我们首先会联想到砸中牛顿的苹果。但这只是我们对引力的体会,实际上,就像地球拉着苹果从树上掉下来一样,苹果对地球也有拉力。只不过地球实在太大,使得其他物体对它的吸引力可以忽略不计。有质量的任意物体之间都有引力作用。

 

公式如下图:

把上面的公式转换成Processing代码如下,我们给Particle类又添加了一个成员函数attract。因为我们知道力是一个矢量,也就说它既有方向,也有大小,所以我们必须对两者同时进行求解。

 

 

最后得到效果如图,可以看见其中一个点在吸引另一个点。

· Curve:画一条曲线

 

因为郭老师的worms是弯弯曲曲的,所以我们有必要先来看一下如何用Processing来画一条贝塞尔曲线。

 

绘制二维平面上的贝塞尔曲线时,需要四个点。其中第二个点P1和第三个点P2用来控制曲线的曲度,P0和P3是曲线的起始点和终止点。下面是贝塞尔曲线的生成过程。

 

Processing中绘制贝塞尔曲线需要调用curve函数。该函数接受8个参数,依次是P1、P0 、P3、 P2所对应的横纵坐标。

 

大家请看下面这个例子。

 

 

效果如图,大家可以自己摸索一下贝塞尔曲线的绘制规律。

 

 

· 判断两条线段的相交

 

郭老师的作品中有一个效果就是新加入的worm会切割和它相交的worm,所以我们得先看看如何判断两条线段相交。

 

判断两条线段的相交是计算机几何中的一个问题,有很多中解决方法,我给大家介绍其中一种。

 

该方法的核心思想是:如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两变。也就是说A、B两点在线段CD的两边,C、D两点在线段AB的两边(见下图)。

 

于是问题就变成了如何判断一个点在一条线段的哪一边。为了区别线段的两边,我们给线段一个方向,并规定该方向的顺时针方向是左边。也就是说下图中C点在向量AB的左边。

 

而C点在向量AB左边的条件是向量AB和向量AC的叉乘所得结果大于等于零,向量A和向量B的叉乘公式如下:

 

AB = (x1, y1), AC = (x2, y2)

AB X AC = x1 * y2 - y1 * x2

 

所以我们可以通过以下的函数来判断两条线段是否相交。

 

 

 

 

 

程序运行结果和控制台输出分别如下图。

 

 

· 大体框架

 

在掌握了一些基本知识后我们就一起来看看郭老师的代码,首先我们还是来看看代码的大体框架。

 

StageManager是用于控制和管理所有worms的一个系统。它的spawnItf函数给这个系统添加一个干扰力,前两个参数是这个系统的位置,第三个参数是力的大小。系统中的所有worms都会收到这个力的作用。

 

在没有新的worm添加进系统的时候,会调用checkVanish方法会删除该系统内所有长度太短的warms。否则会通过checkIntersect判断新加入的worm和已存在的worms是否有相交,如果有的话,就对相交的worms进行切割。

 

鼠标按下的时候StageManager会调用spawnW方法添加一个新的worm。如果鼠标进行了有效的移动,那么在鼠标被释放之前会不断增加新的worm的长度。

 

在这里说明一下,每一个worm是由一系列Vertex构成的,有点像一个一个点,worm就是通过贝塞尔曲线将这一个个点连接起来构成的。

 

分析到现在,大家看懂下面的代码应该不是难事了。

 

 

 

 

 

 

· Class StageManager

 

下面我们来看看StageManager这个类具体是如何定义的。

 

在update函数里面会对系统中的worms施加一些力的作用,用于确定它们以及它们身体的各个部分的新位置。

 

在checkIntersect函数中判断相交的方法很简单,每一次加入一个Vertex后,该worm会多出来一段,而每个worm都是由一段一段构成的,所以只用判断这新加入的一段是否和其他worm的任意一段是否相交即可。

 

当找到相交的一段的时候,就以该段的一个点为分割线,将worm分成两部分,前一部分保留,后一部分删除。并且将后一部分变成一个新的worm。

 

 

 

· Class Worm

 

下面我们来看Worm这个类,其中的spawnV函数确实是给worm添加Vertex,但是只在下面两种情况下添加:

 

- 该worm是新的加入的worm。

- 要添加的Vertext的位置和worm的最后一个节点的距离大于5,这么做的目的之一是防止一直按着鼠标不动的时候给蠕虫添加节点,这样是没有意义的,同时会导致每一条蠕虫含有太多的节点。

 

 

 

 

 

 

 

· Class Vertex

 

下面我们来看最后一个类,也就是worm的组成部分——Vertex。在这个类里,大家着重去看可以对Vertex施加哪些力,这些力的大小、方向是怎样的。

 

 

 
大当家看完郭老师的这个作品之后,一定会觉得他对力的使用有自己的独特理解。其实用算法模拟自然界的力还有很多的用途,在可视化领域有一种叫力导向图的可视化方法(如下图),它的节点就是根据力来布局的。
 

自然界中除了郭老师代码中使用的排斥力、吸引力,还有各种各样的力:摩擦力、流体阻力、重力等等,大家闲暇时刻都可以去模拟试试,看看能不能通过力让自己的作品看上去更加的“生机勃勃”!

 

参考资料

- https://www.jianshu.com/p/8f82db9556d2

- The Nature Of Code ---DANIEL SHIFFMAN