文章目录

  • 引言
  • 代码
    • 大体结构
    • generateNewTree
    • class Branch
    • class Leaf
    • 交互
      • 鼠标
      • 键盘
  • 拓展
  • 小结
  • 参考资料

引言

大家都知道,樱花是日本的国花,他们对于樱花十分的喜爱,所以在日本最常见的就是樱花。其中尤其以富士山的樱花最为美丽。每年在三四月份,日本人就开始去富士山看樱花。

现在不是三四月,大家可能也都不在日本的富士山。但是这并不能阻止我们去赏花!正巧Jason Labbe老师的Cherry blossom fractals就给了我们一个机会看樱花从树上纷纷而落,效果如下。

通过观察不难得到,当我们按下键盘的时候,鼠标周围就会出一下一种类似“爆炸”的效果:树枝震动,花瓣飘落,这也是该作品的亮点之一。

废话不多说,那么接下来我们就一起去看看如何用用Processing实现该效果吧。
  

代码

大体结构

现在我们就正式来看代码。这个作品的结构很清晰,在setup函数中生成一棵新的树,并且在draw函数对构成这个树的树枝和叶子进行更新和绘制。

// 全局变量
// 存储所有的树枝和叶子
ArrayList<Branch> branches = new ArrayList<Branch>();
ArrayList<Leaf> leaves = new ArrayList<Leaf>();// 树的最大层级数
int maxLevel = 9;void setup() {size(800, 700);colorMode(HSB, 100);generateNewTree();
}void draw() {background(100);// 对每一个树枝进行刷新和绘制for (int i = 0; i < branches.size(); i++) {Branch branch = branches.get(i);branch.move();branch.display();}// 对每一片叶子进行刷行和绘制for (int i = leaves.size()-1; i > -1; i--) {Leaf leaf = leaves.get(i);leaf.move();leaf.display();// 不显示屏幕外的叶子leaf.destroyIfOutBounds();}
}

  

generateNewTree

接下来我们来看看如何生成一棵新的树。

在generateNewTree函数首先清空已经有的树枝和树叶,然后生成在第一个树枝(这个树枝在屏幕底部,并且长度是在一定范围里面随机),接着递归调用subDivide这个方法生成整棵树。

在初始化时除了传入该树枝起始点和结束点的坐标,还要传入当前层级数和它的父亲。

void generateNewTree() {// 清空已经存在的树枝和树叶branches.clear();leaves.clear();// 生成第一个树枝float rootLength = random(80.0, 150.0);branches.add(new Branch(width/2, height, width/2, height-rootLength, 0, null));// 递归生成整个树subDivide(branches.get(0));
}

在subDivide函数中给指定的树枝添加1到3个树枝或者叶子。添加树枝还是添加叶子取决于当前的层级数。同于对于添加叶子的树枝还要特别注意:对每一个添加的叶子要设置一个位置的偏移量,否者它们会重叠。


void subDivide(Branch branch) {// 用来存储新的树枝ArrayList<Branch> newBranches = new ArrayList<Branch>();// 确定这个树枝连接的分支的数量int newBranchCount = (int)random(1, 4);// 新的树枝的长度比例范围float minLength = 0.7;float maxLength = 0.85;// 添加新的树枝switch(newBranchCount) {// if(newBranchCount == 2)case 2:newBranches.add(branch.newBranch(random(-45.0, -10.0), random(minLength, maxLength)));newBranches.add(branch.newBranch(random(10.0, 45.0), random(minLength, maxLength)));break;// else if(newBranchCount == 3)case 3:newBranches.add(branch.newBranch(random(-45.0, -15.0), random(minLength, maxLength)));newBranches.add(branch.newBranch(random(-10.0, 10.0), random(minLength, maxLength)));newBranches.add(branch.newBranch(random(15.0, 45.0), random(minLength, maxLength)));break;// elsedefault:newBranches.add(branch.newBranch(random(-45.0, 45.0), random(minLength, maxLength)));break;}// 将新生成的树枝加入全局存储树枝的动态数组for (Branch newBranch : newBranches) {branches.add(newBranch);if (newBranch.level < maxLevel) {// 如果不是最大的深度,那么就继续生成树枝subDivide(newBranch);} else {// 否则随机生成5片叶子float offset = 5.0;for (int i = 0; i < 5; i++) {leaves.add(new Leaf(newBranch.end.x+random(-offset, offset), newBranch.end.y+random(-offset, offset), newBranch));}}}
}

  

class Branch

接下来我们来看看Branch这个类。

在构造函数中对该树枝的起始点、结束点、深度、父亲等属性进行了初始化。树枝的长度、宽度、颜色都是根据它的层级数来决定的。

给当前树枝添加分支的时候,分支有两个特点:第一,起始点是当前树枝的结束点;第二,角度和长度由newBranch函数传入的参数所决定。

需要注意的是:在绘制树枝的时候,对于有父亲的树枝,并不是直接从该树枝的起始点到结束点画一条直线,而是从其父亲的结束点到该树枝结束点之间画一条直线。等看完文章后,大家可以思考一下这是为什么。

接下来我们看看让树枝出现视频中震动效果的applyForce、sim、move这三个函数。其中applyForce是给树枝施加力的作用,改变其加速度。而move是通过树枝的速度、加速度来更新树枝的结束点的位置。至于树枝受到怎样的力,是由sim函数所决定。

下面我们来看看sim函数。在该函数中我们给树枝施加了两个力的作用。一个力是和树枝运动速度相反,作用是让树枝回到初始的位置的时间更短。第二力的作用是让树枝当前结束节点回到初识时候的位置。具体的实现方式请大家看代码。

class Branch {// 树枝的起始点和结束点PVector start;PVector end;// 描述树枝运功状态的参数(速度、加速度)PVector vel = new PVector(0, 0);PVector acc = new PVector(0, 0);// 树枝在不动的时候结束点的位置PVector restPos;// 树枝的深度、父亲、长度int level;Branch parent = null;float restLength;Branch(float _x1, float _y1, float _x2, float _y2, int _level, Branch _parent) {this.start = new PVector(_x1, _y1);this.end = new PVector(_x2, _y2);this.level = _level;this.restLength = dist(_x1, _y1, _x2, _y2);this.restPos = new PVector(_x2, _y2);this.parent = _parent;}void display() {// 根据深度确定颜色和宽度stroke(10, 57, 20+this.level*4);strokeWeight(maxLevel-this.level+1);// 绘制树枝if (this.parent != null) {line(this.parent.end.x, this.parent.end.y, this.end.x, this.end.y);} else {line(this.start.x, this.start.y, this.end.x, this.end.y);}}// 获得一个新的树枝Branch newBranch(float angle, float mult) {// 确定当前树枝的长度PVector direction = new PVector(this.end.x, this.end.y);direction.sub(this.start);float branchLength = direction.mag();// 确定新的树枝的长度和角度float worldAngle = degrees(atan2(direction.x, direction.y))+angle;direction.x = sin(radians(worldAngle));direction.y = cos(radians(worldAngle));direction.normalize();direction.mult(branchLength*mult);PVector newEnd = new PVector(this.end.x, this.end.y);newEnd.add(direction);return new Branch(this.end.x, this.end.y, newEnd.x, newEnd.y, this.level+1, this);}// 施加力的作用void applyForce(PVector force) {PVector forceCopy = force.get();// 深度越大的树枝被施加的力越小float divValue = map(this.level, 0, maxLevel, 8.0, 2.0);forceCopy.div(divValue);this.acc.add(forceCopy);}void sim() {// 施加一个和速度方向相反的力PVector airDrag = new PVector(this.vel.x, this.vel.y);float dragMagnitude = airDrag.mag();airDrag.normalize();airDrag.mult(-1);// 设置该力的大小airDrag.mult(0.05*dragMagnitude*dragMagnitude); this.applyForce(airDrag);// 施加一个弹簧力的效果PVector spring = new PVector(this.end.x, this.end.y);spring.sub(this.restPos);float stretchedLength = dist(this.restPos.x, this.restPos.y, this.end.x, this.end.y);spring.normalize();float elasticMult = map(this.level, 0, maxLevel, 0.1, 0.2); spring.mult(-elasticMult*stretchedLength);this.applyForce(spring);}// 更新树枝的状态void move() {this.sim();// 让速度随着时间变得越来越慢this.vel.mult(0.95);// 将低于此阈值的速度设为0以减少抖动if (this.vel.mag() < 0.05) {this.vel.mult(0);}this.vel.add(this.acc);// 改变树枝结束点的位置this.end.add(this.vel);this.acc.mult(0);    }
}

  

class Leaf

下面我们来看看Leaf这个类。

对于每一片叶子Leaf,它有三个状态:

  • 在树枝上:这时候的叶子不受任何力的作用,它会停留在枝头。
  • 下落状态:这时候的叶子受到一个向下的重力,它会慢慢飘落。
  • 在地上:这时候的叶子不受同样不受任何力的作用,并且竖直方向的速度为零。

剩下的代码大家应该很好理解了。

class Leaf {// 描述叶子运动状态的参数(位置,速度,加速度)PVector pos;PVector vel = new PVector(0, 0);PVector acc = new PVector(0, 0);// 叶子的一些属性float diameter;float opacity;float hue;float sat;PVector offset;// 叶子是否下落boolean dynamic = false;Branch parent;Leaf(float _x, float _y, Branch _parent) {this.pos = new PVector(_x, _y);this.diameter = random(2.0, 8.0);this.opacity = random(5.0, 50.0);this.parent = _parent;this.offset = new PVector(_parent.restPos.x-this.pos.x, _parent.restPos.y-this.pos.y);// 设置颜色的色调和饱和度if (leaves.size() % 5 == 0) {this.hue = 5;this.sat = 100;} else {this.hue = random(75.0, 95.0);this.sat = 50;}}void display() {// 绘制noStroke();fill(this.hue, sat, 100, this.opacity);ellipse(this.pos.x, this.pos.y, this.diameter, this.diameter);}void bounds() {// 判断是否落地,如果是那么将竖直方向的速度设为0if (! this.dynamic) {return;}float ground = height-this.diameter*0.5;if (this.pos.y > ground) {this.vel.y = 0;this.vel.x *= 0.95;this.pos.y = ground;}}void applyForce(PVector force) {this.acc.add(force);}void move() {if (this.dynamic) {// 在下落中// 施加一个重力PVector gravity = new PVector(0, 0.025);this.applyForce(gravity);this.vel.add(this.acc);this.pos.add(this.vel);this.acc.mult(0);this.bounds();} else {// 不是在下落中this.pos.x = this.parent.end.x+this.offset.x;this.pos.y = this.parent.end.y+this.offset.y;}}// 如果超出了范围就删除void destroyIfOutBounds() {if (this.dynamic) {if (this.pos.x < 0 || this.pos.x > width) {leaves.remove(this);}} }
}

  

交互

鼠标

我们现在来这个作品的交互部分。首先是鼠标,这个部分非常的简单,就是删除旧的树,并且生成一棵新的树。

void mousePressed() {generateNewTree();
}

  

键盘

其次是键盘,这个部分的代码看上去比较复杂,但是思路非常的简单:计算所有树枝的结束点和树叶到鼠标光标的距离,如果距离小于某个阈值话,就它们施加一个远离鼠标的力,达到“爆炸”的效果。

// 返回两个点距离的平方
float distSquared(float x1, float y1, float x2, float y2) {return (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
}void keyPressed() {PVector source = new PVector(mouseX, mouseY);// 对树枝施加力的作用float branchDistThreshold = 300*300;for (Branch branch : branches) {// 只对相对于当前鼠标光标位置内一定范围的树枝施加力的作用float distance = distSquared(mouseX, mouseY, branch.end.x, branch.end.y);if (distance > branchDistThreshold) {continue;}// 施加一个远离鼠标的力,该力离鼠标越远则越小PVector explosion = new PVector(branch.end.x, branch.end.y);explosion.sub(source);explosion.normalize();float mult = map(distance, 0, branchDistThreshold, 6.0, 1.0); explosion.mult(mult);branch.applyForce(explosion);}// 对树叶施加力的作用float leafDistThreshold = 50*50;for (Leaf leaf : leaves) {// 只对相对于当前鼠标光标位置内一定范围的树叶施加力的作用float distance = distSquared(mouseX, mouseY, leaf.pos.x, leaf.pos.y);if (distance > leafDistThreshold) {continue;}// 施加一个远离鼠标的力,该力离鼠标越远则越小PVector explosion = new PVector(leaf.pos.x, leaf.pos.y);explosion.sub(source);explosion.normalize();float mult = map(distance, 0, leafDistThreshold, 2.0, 0.1);mult *= random(0.8, 1.2); explosion.mult(mult);leaf.applyForce(explosion);// 将树叶设置为飘落状态leaf.dynamic = true;}
}

  

拓展

该作品有很多可以改进和拓展的地方。

程鹏老师提到:“如果落下来的花朵用图片代替,同时落下来的感觉更慢一点,然后加入一个风吹的效果,就会更好看了。”那么这些就算是给大家留下的课后作业。

然后我在上图Santiago Fiorino老师的Orbits基础上实现了下图Tree Planet的效果。


在该作品中,当用户点击星球的主体部分的时候,会种下一棵新的树,然后这棵树会生长、发芽、开花。需要注意的是:该星球上最多只能有三棵树,也就是说当用户加入第四棵树的时候,第一棵树的花瓣会飘落,落入星球消失不见,同时树枝也会逐渐消失。这是不是有一种“落红不是无情物,化作春泥更护花的感觉?”

另外当鼠标在星球的主体部分外移动的时候,会对该星球进行旋转。

细心的同学可以发现Tree Planet用到了很多之前代码检测所提到的技术,感兴趣的同学可以去上面的网址了解在线效果和代码。
  

小结

今天的代码检测带大家初步了解了一下分形,但是分形的魅力远不止与此:比如下面的这片雪花。

大家感兴趣的话可以尝试去实现一下,这样我们不仅能去“富士山”看樱花从树上纷纷而落,甚至还可以时光倒流,去看2002年的第一场雪呢!

  

参考资料

  • the nature of code – by Daniel Shiffman

Processing 案例 | 去“富士山”看樱花从树上纷纷而落相关推荐

  1. 春天来了,去武大看樱花

    樱花,樱花!    寻春,从樱花来寻起.观赏樱花的最佳时间只有短短的十几天,但绽放时的绚烂光景,令人无限着迷.上周末,记者就在武汉大学和东湖樱花园里看到,不少早开的樱花已经跃上枝头,赏樱的市民也是络绎 ...

  2. Dijkstra——去北京看奥运

    Description 2008年将到,王飞同学化了九牛二虎之力搞到了2张2008年奥运会足球赛决赛的门票.真是开心啊!他爸爸准备开车跟他一起去北京看球赛.不过门票费好贵啊,所以他爸爸说了,这个钱要在 ...

  3. arma模型_Eviews经典案例 | 初学者必看!ARMA模型精讲

    [本期分析师介绍]希音老师,<数据分析学堂>金牌分析师,对eviews的时间序列.ARMA.VAR.VECM.ARCH.GARCH等操作有深入的研究和实战经验,累计服务客户1000+.今天 ...

  4. 去安定医院看失眠,有必要吗?

    这是一篇关于失眠治疗的100%真实体验文. 故事是这样的,我在去年年初诊断出了广泛性焦虑症和植物神经紊乱.当时也接受了很多治疗,包括药物干预.焦虑症的症状初步缓解之后,失眠却严重了起来.加上工作本身非 ...

  5. 想去再看一遍《龙猫》

    龙猫这部电影,每一次看到都让自己想起暖暖的回忆.虽然故事发生的日本,但是很多细节都和自己小时候类似. 整部电影没有什么华丽的画面,但却简单而美好. 影片刚开始,爸爸带着小月和小梅搬家到乡下,搬进一间破 ...

  6. 面向对象:杭州的下雪天,想带你去湖心亭看雪

    各位男生如果看到合适的女生,但自己 "下不了手",请可劲地介绍给你还单着的亲朋好友 ^_^  小帖士 1)男生和女生都可以报名参加面向对象,加入方式详见公号底部菜单  " ...

  7. 我发现曾经你真正去深入弄过的东西,你很长时间没碰,再去回看之前笔记也能很快捡起来。

    我发现曾经你真正去深入弄过的东西,你很长时间没碰,再去回看之前笔记也能很快捡起来. 所以你之前真正认真学过的东西,是不会白费的. 做过的项目也是的 我觉得考研也是的,如果你第二遍再来,应该会轻松些. ...

  8. 去鸟巢看奥运时的照片。

    去鸟巢看奥运时的照片. 表情有点像

  9. 喜欢的女孩想看樱花,安排!将静态网页部署在Coding远端!她说棒极了~

    前言 首先准备一份"樱花飘落"源代码,然后注册一个coding账号 想要获取樱花源代码的希望点个赞!后台私信我!这也是我创作的动力! coding注册流程 根据提示填入相关信息即可 ...

最新文章

  1. 直播协议的选择:RTMP vs. HLS
  2. 【软件】我安装Tomcat5.5(Java Web开发服务器)的几点经验
  3. 使用IBM Blockchain Platform extension开发你的第一个fabric智能合约
  4. 数组转换成json key-value形式
  5. use putty to log on OS
  6. BIND9之dig工具的使用方法
  7. 计算神经生物学 计算机转生物,计算神经生物学的应用有哪些?
  8. easyui常用控件样式收藏
  9. php环境下cache失效,cache缓存失效高并发读数据库的问题
  10. 网络面试100道(路由交换)
  11. 通话清晰的蓝牙耳机推荐,打电话专用的耳机盘点
  12. Windows10查看便签
  13. Sophix热修复的简单使用
  14. 由边际成本引发的思考
  15. 如何做好预算管理?预算上接战略,下接绩效,如何形成战略-计划-预算-绩效的闭环?
  16. 未来的计算机 展望未来作文,展望未来作文(通用10篇)
  17. 学生可以用计算机干什么,好学生用电脑干什么
  18. Bash 中的字符串变量
  19. 移动端vue+vant+高德地图实现拖拽选址,周边选址,搜索选址,自动定位,选择城市功能,获取地址经纬度,详细地址
  20. java 由低位到高位倒序输出

热门文章

  1. 使用VIM是一种信仰
  2. 【报告分享】2022快手磁力金牛家居百货行业营销洞察报告-磁力引擎(附下载)
  3. qt 批量裁剪图片_照片变素描,不用下载App,好用的在线图片处理及图库
  4. Axure RP 基于母版实现页面框架/页眉/页脚/导航栏
  5. Quartus-ii的LPT1编程硬件配置问题
  6. CSCD.中国科学引文数据库核心库和引文库来源期刊列表(2007年-2008年)
  7. Mermaid知识点总结3 - Flowchart 2
  8. 量化投资中常用python代码分析(一)
  9. 论文解读:医学影像中的注意力机制
  10. 无招胜有招——面向对象设计模式随笔,