文章目录

  • 引言
  • 代码
    • 大体框架
    • Class StageManager
    • Class Interference
    • Class Worm
    • Class Vertex
  • 小结
  • 参考资料

引言

最近在各大美术学院做讲座的郭锐文(Raven Kwok)先生,一个视觉艺术家兼创意型程序员,他用Processing写了一个名叫“p0629a_2013_worms”的作品,效果如下:

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

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

心急的同学可以和往常一样,先去郭老师的OpenProcessing上看看在线的效果。
  

代码

大体框架

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

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

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

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

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

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

StageManager sm;
boolean onPressed;
boolean newVertexSpawn;
int currentDrawingWormIndex;void setup() {size(500, 500);colorMode(HSB);smooth();frameRate(30);sm = new StageManager();sm.spawnItf(width/2, height/2, 0.001);
}void draw() {background(255);if (onPressed) {Worm currentW = (Worm) sm.worms.get(currentDrawingWormIndex);currentW.spawnV(mouseX, mouseY);// 判断鼠标是否进行了有效的移动if (newVertexSpawn) { sm.checkIntersect();}} else {sm.checkVanish(); }sm.update();sm.display();
}void mousePressed() {sm.spawnW(); currentDrawingWormIndex = sm.worms.size()-1;onPressed = true;
}void mouseReleased() {onPressed = false;
}class StageManager{};
class Worm{};
class Vertex{};
class Interference{};

  

Class StageManager

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

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

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

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

class StageManager {ArrayList worms;ArrayList itfs;int maxPAmt;StageManager() {worms = new ArrayList(); //存储所有的wormsitfs = new ArrayList(); //存储所有的干扰力}void update() {for (int i=0; i<worms.size(); i++) {Worm eachW = (Worm) worms.get(i);eachW.repelWithin(); // 防止worm缩成一团eachW.tensedWithin(); // 用于worm形成锯齿状// worm间相互排斥for (int j=i+1; j<worms.size(); j++) {Worm repWorm = (Worm) worms.get(j);eachW.repelWithOther(repWorm);}// worm收到系统中干扰力的影响for (int k=0; k<itfs.size(); k++) {Interference eachItf = (Interference) itfs.get(k);eachW.interferedBy(eachItf, eachItf.force);}eachW.update();}}// 绘制每一个wormvoid display() {for (int i=0; i<worms.size(); i++) {Worm eachW = (Worm) worms.get(i);eachW.display();}}// 将系统中长度过短的worm删除void checkVanish() {for (int i = worms.size()-1; i>=0; i--) {Worm eachW = (Worm) worms.get(i);if (eachW.vertices.size()<=3) {worms.remove(i);}}}// 判断是否有相交的worms,如果有,就对其进行分割void checkIntersect() {Worm currentW = (Worm) worms.get(currentDrawingWormIndex);int lastVertexIndex = currentW.vertices.size()-2;if (lastVertexIndex>=0) {Vertex currentVertex = (Vertex)currentW.vertices.get(lastVertexIndex+1);Vertex lastVertex = (Vertex)currentW.vertices.get(lastVertexIndex);// 新添加的蠕虫新加入的一段的端点坐标PVector cSeg1v1 = new PVector(currentVertex.loc.x, currentVertex.loc.y);PVector cSeg1v2 = new PVector(lastVertex.loc.x, lastVertex.loc.y);for (int i=0; i<worms.size(); i++) {if (i==currentDrawingWormIndex) {continue;} else {Worm eachW = (Worm) worms.get(i);for (int j=1; j<eachW.vertices.size()-2; j++) {// 获得需要进行相交检测的worm的其中一段,并获得其端点坐标Vertex v1 = (Vertex)eachW.vertices.get(j);Vertex v2 = (Vertex)eachW.vertices.get(j+1);PVector cSeg2v1 = new PVector(v1.loc.x, v1.loc.y);PVector cSeg2v2 = new PVector(v2.loc.x, v2.loc.y);// 进行相交检测if (intersect(cSeg1v1, cSeg1v2, cSeg2v1, cSeg2v2)) {Worm cutOffSeg = new Worm();worms.add(cutOffSeg);for (int k=j; k<eachW.vertices.size(); k++) {Vertex ogV = (Vertex)eachW.vertices.get(k);Vertex addedV;if (k==j) {// 第一个Vertex是中点addedV = new Vertex((v1.loc.x+v2.loc.x)*0.5, (v1.loc.y+v2.loc.y)*0.5);} else {addedV = new Vertex(ogV.loc.x, ogV.loc.y);}cutOffSeg.vertices.add(addedV);}v1.loc.x = (v1.loc.x+v2.loc.x)*0.5;v1.loc.y = (v1.loc.y+v2.loc.y)*0.5;//删除后一段for (int k=eachW.vertices.size()-1; k>j+1; k--) {eachW.vertices.remove(k);}}}}}}}// 判断两条线段是否相交boolean intersect(PVector s1v1, PVector s1v2, PVector s2v1, PVector s2v2) {float denominator = (s1v1.x-s1v2.x)*(s2v1.y-s2v2.y)-(s1v1.y-s1v2.y)*(s2v1.x-s2v2.x);if (denominator==0) {return false;} else {float istX = ((s1v1.x*s1v2.y-s1v2.x*s1v1.y)*(s2v1.x-s2v2.x)-(s1v1.x-s1v2.x)*(s2v1.x*s2v2.y-s2v2.x*s2v1.y))/denominator;float istY = ((s1v1.x*s1v2.y-s1v2.x*s1v1.y)*(s2v1.y-s2v2.y)-(s1v1.y-s1v2.y)*(s2v1.x*s2v2.y-s2v2.x*s2v1.y))/denominator;if ( (istX-s1v1.x)*(istX-s1v2.x)<=0 && (istY-s1v1.y)*(istY-s1v2.y)<=0 && (istX-s2v1.x)*(istX-s2v2.x)<=0 && (istY-s2v1.y)*(istY-s2v2.y)<=0 ) {return true;} else {return false;}}}void spawnW() {Worm addedW = new Worm();worms.add(addedW);}// 添加干扰力void spawnItf(float initX, float initY, float force) {Interference addedItf = new Interference(initX, initY, force);itfs.add(addedItf);}
}

  

Class Interference

这个类很简单,就是前面提到的影响所有worms的干扰力。

class Interference {PVector loc;// 力的大小float force;// 力的位置Interference(float initX, float initY, float force) {this.force = force; loc = new PVector(initX, initY); }
}

  

Class Worm

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

  • 该worm是新的加入的worm。
  • 要添加的Vertext的位置和worm的最后一个节点的距离大于5,这么做的目的之一是防止一直按着鼠标不动的时候给蠕虫添加节点,这样是没有意义的,同时会导致每一条蠕虫含有太多的节点。
class Worm {ArrayList vertices;Worm() {vertices = new ArrayList();}void update() {for (int i=0; i<vertices.size(); i++) {Vertex eachV = (Vertex) vertices.get(i);eachV.update();}}//worm的每个Vertex间相互排斥void repelWithin() {for (int i=1; i<vertices.size()-1; i++) {Vertex eachV = (Vertex) vertices.get(i);for (int j=i+1; j<vertices.size()-1; j++) {Vertex repV = (Vertex) vertices.get(j);eachV.repel(repV);}}}//worm的每个Vertex和另一个worm的每个Vertex相互排斥void repelWithOther(Worm repWorm) {for (int i=1; i<vertices.size()-1; i++) {Vertex eachV = (Vertex) vertices.get(i);for (int j=1; j<repWorm.vertices.size()-1; j++) {Vertex repV = (Vertex) repWorm.vertices.get(j);eachV.repel(repV);}}}// 让worm拥有锯齿的效果void tensedWithin() {for (int i=1; i<vertices.size()-1; i++) {Vertex eachV = (Vertex) vertices.get(i);Vertex pV1 = (Vertex) vertices.get(i-1);Vertex nV1 = (Vertex) vertices.get(i+1);eachV.tensedBy(pV1, nV1, 0.01);if (i>1&&i<vertices.size()-2) {Vertex pV2 = (Vertex) vertices.get(i-2);Vertex nV2 = (Vertex) vertices.get(i+2);eachV.tensedBy(pV1, nV2, 0.005);eachV.tensedBy(pV2, nV1, 0.005);eachV.tensedBy(pV2, nV2, 0.005);}}}// 干扰力对构成worm的每一个Vertex都造成影响void interferedBy(Interference itf, float force) {for (int i=0; i<vertices.size(); i++) {Vertex eachV = (Vertex) vertices.get(i);eachV.interferedBy(itf, force);}}// 每一条worm是由一段段贝塞尔曲线构成的void display() {noFill();stroke(0);strokeWeight(2);if (vertices.size()>3) {for (int i=1; i<vertices.size()-2; i++) {Vertex control1 = (Vertex) vertices.get(i-1);Vertex draw1 = (Vertex) vertices.get(i);Vertex draw2 = (Vertex) vertices.get(i+1);Vertex control2 = (Vertex) vertices.get(i+2);curve(control1.loc.x, control1.loc.y, draw1.loc.x, draw1.loc.y, draw2.loc.x, draw2.loc.y, control2.loc.x, control2.loc.y);}}}// 添加Vertexvoid spawnV(float initX, float initY) {if (vertices.size()==0) {Vertex addedV = new Vertex(initX, initY);vertices.add(addedV);newVertexSpawn = true;} else {Vertex lastV = (Vertex) vertices.get(vertices.size()-1);if (dist(initX, initY, lastV.loc.x, lastV.loc.y)>=5) {Vertex addedV = new Vertex(initX, initY);vertices.add(addedV);newVertexSpawn = true;} else {newVertexSpawn = false;}}}
}

  

Class Vertex

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

class Vertex {PVector loc, vel, acc;float thres, thresT, decay;Vertex(float initX, float initY) {loc = new PVector(initX, initY);vel = new PVector(0, 0);acc = new PVector(0, 0);decay = 0.9; thres = 0;thresT = 20;}void update() {thres = lerp(thres, thresT, 0.1);vel.add(acc);vel.limit(10);// 减小速度,让Vertex能停下来vel.mult(decay); loc.add(vel);acc.set(0, 0, 0);}// 施加Vertex之间的排斥力void repel(Vertex repV) {PVector dir = new PVector(0, 0);float repForce = 0;dir = PVector.sub(loc, repV.loc);// 防止Vertex之间距离太远if (dir.mag() < thres) {repForce = 1/(dir.mag()+1);dir.normalize();dir.mult(repForce);acc.add(dir);repV.acc.sub(dir);}}// 施加系统中的干扰力void interferedBy(Interference itf, float force) {PVector dir = new PVector(0, 0);float distance = 0;dir = PVector.sub(itf.loc, loc);distance = dir.mag();dir.normalize();dir.mult(distance*force);acc.add(dir);}// 一个指向Vertex v1,v2中点的力void tensedBy(Vertex v1, Vertex v2, float force) {PVector mid = new PVector((v1.loc.x+v2.loc.x)*0.5, (v1.loc.y+v2.loc.y)*0.5);PVector dir = new PVector(0, 0);float distance = 0;dir = PVector.sub(mid, loc);distance = dir.mag();dir.normalize();dir.mult(distance*force);acc.add(dir);}void display() {ellipse(loc.x, loc.y, 5, 5);}
}

  

小结

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

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

参考资料

  • The Nature Of Code —DANIEL SHIFFMAN

Processing 案例 | 郭锐文先生的 worms相关推荐

  1. 5分绩点转4分_U19男篮世界杯 | 郭昊文空砍23分4篮板5助攻 国青72-86负菲律宾

    北京时间7月6日,2019年U19男篮世界杯在希腊继续进行,在13-16名排位赛中,中国男篮迎战菲律宾男篮.郭昊文空砍23分4篮板5助攻,王泉泽仅得5分,最终中国男篮以72-86不敌菲律宾. 在U19 ...

  2. 锐文科技智能网卡xNIC-200/400在国产飞腾FT2000测试性能报告

    锐文科技的xNIC系列智能网卡为自主研发,具有自主知识产权的国产智能网卡. 测试平台搭建使用第三方测试工具,测试平台搭建如图1所示 图1:基于飞腾CPU的测试平台 飞腾CPU(ARM)服务器硬件配置: ...

  3. 锐文科技智能网卡xNIC-200/400在国产服务器测试性能报告

    锐文科技,依托于网络虚拟化,软件定义网络,网络安全等前沿技术:专注于基于FPGA的高性能网络产品开发:为云计算,高性能计算,企业网络及金融领域等提供高带宽,低延迟的智能网卡(Smart NIC).公司 ...

  4. 天津戏法的记忆——魔术名家郭玉文老师访谈录

    爱学习,勤思考:学数学,玩魔术.欢迎点击头部蓝字关注MatheMagician,这里有你要的奇迹! 机缘巧合 在我这次6月初的天津的回忆旅程中,让我满足和有意外惊喜的一件事当属离开前一天的下午,和久未 ...

  5. python机器学习案例系列教程——文档分类器,朴素贝叶斯分类器,费舍尔分类器

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 github地址:https://github.com/626626cdllp/data-mining/tree/master/Bay ...

  6. Processing 案例 | 去“富士山”看樱花从树上纷纷而落

    文章目录 引言 代码 大体结构 generateNewTree class Branch class Leaf 交互 鼠标 键盘 拓展 小结 参考资料 引言 大家都知道,樱花是日本的国花,他们对于樱花 ...

  7. Processing 案例 | 扑面而来的满天繁星

    文章目录 引言 效果展现 原理分析 代码实现 确定代码执行流程 星星绘制在屏幕上 让星星动起来 点击鼠标,星星的速度发生改变 视角改变 完整代码 结语    引言 清明时节雨纷纷,路上行人欲断魂&qu ...

  8. Processing 案例 | 诡异的八爪鱼

    文章目录 引言 代码 主要结构 class Squid class Tentacle class Limb 拓展 小结 引言 冬天到了,春天还会远吗?七月到了,八月还会远吗? 我们迎来了七月的烈日炎炎 ...

  9. Processing 案例 | 用粒子系统谱写冰与火之歌

    文章目录 引言 FireBrush 分析作品 大概流程 父类:Particle 子类:Fire 子类:Smoke IceAndFire 更多拓展 Frozen Brush 愤怒的小鸟 小结 引言 前不 ...

最新文章

  1. 消息云服务器,对方启用云消息服务器
  2. 头文件的查找方式和库的搜索路径
  3. 【Visual C++】游戏开发笔记二十一 游戏基础物理建模(三) 摩擦力系统模拟
  4. CentOS中设置ip地址等信息
  5. 【转】论对东西的崇拜
  6. 微软已暂时停用你的帐户_Apple ID 被停用如何解决?
  7. c++ 前缀 变量命名_前缀命名
  8. 云服务器(Centos)部署SVN
  9. Meteor框架创建示例项目todos的问题
  10. Node.js 抓取电影天堂新上电影节目单及ftp链接
  11. centos7 开机启动文件路径_centos7定时运行python脚本
  12. 开源管理系统OSSIM设置 语言为中文简体
  13. 物联网卡11位和13位号码的区别在于哪
  14. WPF: 自动设置Owner的ShowDialog
  15. 泰然的粒子编辑器~~拿过来玩玩啊
  16. mall-accounts.json ES测试数据
  17. Blender学习入门(一)-Blender的下载和插件安装
  18. 科学网肖波:海外博后申请的一点经验与看法
  19. 笔记本电脑重启快捷键是什么
  20. 快速找出两个Word文档之间的差别

热门文章

  1. jenkins配置qq邮箱
  2. Windows系统安装绿色版MySQL8
  3. m4a转mp3简单的音频转换方法?
  4. nodejs实现分解质因数的算法
  5. syn flood攻击原理及防范
  6. 被繁杂的数据搞到头大?让 Google Cloud 大数据平台帮你实现快准狠!
  7. 【论文精读】2016-CVPR-Learning temporal regularity in video sequences
  8. 一篇文章带你了解,App 测试工具
  9. 手机版本android升级包下载,ColorOS8.0升级包下载安装-oppo手机系统ColorOS8.0正式版升级包下载 安卓版 v1.0- 游娱下载站...
  10. python word2vector 词 财务报告 指数_使用Python可视化Word2vec的结果