本次基于processing创作了一个应用,主要参考书籍为《代码本色》
主要应用的技术为下面三部分:
首先简单介绍一下这个系统
1. 整个主体是一棵随风摆动的树,你可以通过点击空白处来得到不同形状的独一无二的树
关于分形,首先需要了解,分形的两个重要特征:自相似性、递归。
首先比较简单的一种分型树规则是:
1. 画一段线段
2. 在线段末尾(a)向左旋转画一条更短的线段 (b)向右旋转画一条更短的线段
3. 不断地在新线段上重复步骤二
4.设置递归终止条件
这样那你就可以构建一棵树枝是线段的简单分型树。那么在分形树中加入一点点随机性就可以使它的外形更贴近自然。查看一棵树的外形,你 会发现每根树枝的角度和长度都不相同,此外,每根树枝的分支数量也不相同。简单地改变树枝的角度和长度很容易实现,只要在绘制时获取一个随机数即可。你也可以选择一个随机数作为树枝的数量,只需改变branch()函数的调用次数。
同时为了获得更逼真的树,请可以用Perlin噪声算法确定树枝的长度。随时间调整噪声值,同时可以用toxiclibs模拟树的物理效果,每一根树枝都是用弹簧相连的两个粒子,这样就可以获得树木被风吹动的效果。
其中Perlin噪声算法表现出了一定的自然性,因为它能生成符合自然排序(“平滑”)的伪随机数序列。Processing内置了Perlin噪声算法的实现:noise()函数。noise()函数可以有1~3个参数,分别代表一维、二维和三维的随机数。Processing的noise()函数告诉我们噪声是通过几个“八度”计算出来的。调用noiseDetail()函数会改变“八度”的数量以及各个八度的重要性,这反过来会影响noise()函数的行为。
关于Perlin噪声不作更多详谈,指路传送门:
不只是噪声,更是数学美 ---浅谈Perlin Noise
不只是噪音
代码如下:
class branch
{
PVector[] location;
float[] thickness;
int[][] baseIndex = new int[2][];
// float[] baseDtheta;
boolean isCandidate = false;
float[] dTheta;
branch(PVector loc, float thic, int id, int branchIndex)
{
location = new PVector[1];
thickness = new float[1];
location[0] = new PVector(loc.x, loc.y);
thickness[0] = thic;
baseIndex[0] = new int[1];
baseIndex[1] = new int[1];
baseIndex[0][0] = id;
baseIndex[1][0] = branchIndex;
}
void branchRotate(int index, float theta, PVector reference)
{
location[index].sub(reference);
rotate2D(location[index], theta);
location[index].add(reference);
}
void rotate2D(PVector v, float theta)
{
float xTemp = v.x;
v.x = v.x * cos(theta) - v.y * sin(theta);
v.y = xTemp * sin(theta) + v.y * cos(theta);
}
}
class frontier
{
PVector location;
PVector velocity;
float thickness;
boolean finished;
frontier(PVector startPoint, PVector direction)
{
location = new PVector(startPoint.x, startPoint.y);
velocity = new PVector(direction.x, direction.y);
thickness = random(10, 30);
finished = false;
}
frontier(frontier parent)
{
location = parent.location.get();
velocity = parent.velocity.get();
thickness = parent.thickness;
parent.thickness = thickness;
finished = parent.finished;
}
void update(float factor)
{
if( location.x > -10
& location.y > -10
& location.x < width + 10
& location.y < height + 10
& thickness > factor)
{
velocity.normalize();
PVector uncertain = new PVector(random(-1, 1), random(-1, 1));
uncertain.normalize();
uncertain.mult(0.2);
velocity.mult(0.8);
velocity.add(uncertain);
velocity.mult(random(8, 15));
location.add(velocity);
}
else
{
finished = true;
}
} // void update()
}
class tree
{
PVector[] map;
branch[] twig;
int treeSize;
float BranchLengthFactor = 0.3;
float BranchLocationFactor = 0.3;
float dt = 0.025;
float time = 0;
float[] dtheta;
int candNum = 3;
int[] candidateIndex = new int[candNum];
float[] amplitude = new float[candNum];
float[] phaseFactor = new float[candNum];
float freq;
float period;
tree(PVector startPoint, PVector direction)
{
int id = 0;
boolean growth = false;
frontier[] fr = new frontier[1];
fr[id] = new frontier(startPoint, direction);
twig = new branch[1];
twig[id] = new branch(fr[id].location, fr[id].thickness, id, 0);
map = new PVector[1];
map[0] = new PVector(id, twig[id].location.length - 1);
while(!growth)
{
int growthSum = 0;
for(id = 0; id < fr.length; id++)
{
fr[id].update(BranchLocationFactor);
if(!fr[id].finished)
{
twig[id].location = (PVector[]) append(twig[id].location, new PVector(fr[id].location.x, fr[id].location.y));
twig[id].thickness = (float[]) append(twig[id].thickness, fr[id].thickness);
map = (PVector[]) append(map, new PVector(id, twig[id].location.length - 1));
if (random(0, 1) < BranchLengthFactor) // control length of one branch
{
fr[id].thickness *= 0.65;
twig[id].thickness[twig[id].thickness.length - 1] = fr[id].thickness;
if( fr[id].thickness > BranchLocationFactor) // control the number of the locations on all branches, i.e., treeSize.
{
fr = (frontier[]) append(fr, new frontier(fr[id]));
twig = (branch[]) append(twig, new branch(fr[id].location, fr[id].thickness, id, twig[id].location.length - 1));
int _id = id;
if(_id != 0) for(int _i = 0; _i < 2; _i++) twig[twig.length - 1].baseIndex[_i] = concat(twig[twig.length - 1].baseIndex[_i], twig[_id].baseIndex[_i]);
}
} // if (random(0, 1) < 0.2)
}
else growthSum += 1;
}
if(growthSum == fr.length)
{
dtheta = new float[twig.length];
treeSize = map.length;
growth = true;
}
} // while(!growth)
ArrayList _candList = new ArrayList();
float[] _candfloat = new float[twig.length];
for(int i = 0; i < twig.length; i++)
{
_candfloat[i] = (float)twig[i].location.length;
_candList.add(_candfloat[i]);
}
candidateIndex[0] = 0;
twig[0].isCandidate = true;
twig[0].dTheta = new float[twig[0].location.length];
_candfloat[0] = -1.0;
_candList.set(0, -1.0);
for(int i = 1; i < candNum; i++)
{
float _temp = max(_candfloat);
candidateIndex[i] = _candList.indexOf(_temp);
twig[candidateIndex[i]].isCandidate = true;
twig[candidateIndex[i]].dTheta = new float[twig[candidateIndex[i]].location.length];
_candfloat[candidateIndex[i]] = -1.0;
_candList.set(candidateIndex[i], -1.0);
}
// println(candidateIndex);
amplitude[0] = random(0.006, 0.012);
phaseFactor[0] = random(0.6, 1.2);
freq = random(0.5, 0.8);
period = 1 / freq;
for(int i = 1; i < candNum; i++)
{
amplitude[i] = amplitude[i-1] * random(0.9, 1.4);
phaseFactor[i] = phaseFactor[i-1] * random(0.9, 1.4);
}
}
// shu de zhi gan
void swing()
{
for(int i = 0; i < candNum; i++)
{
int _num = twig[candidateIndex[i]].location.length;
for(int j = 0; j < _num; j++) twig[candidateIndex[i]].dTheta[j] = amplitude[i] * dt * TWO_PI * freq * cos(TWO_PI * freq * time - phaseFactor[i] * PI * (float)j / (float)_num);
}
for(int id = 0; id < twig.length; id++)
{
if(twig[id].isCandidate) for(int _id = 1; _id < twig[id].location.length; _id++) twig[id].branchRotate(_id, twig[id].dTheta[_id], twig[id].location[0]);
for(int j = 0; j < twig[id].baseIndex[0].length; j++)
{
if(!twig[twig[id].baseIndex[0][j]].isCandidate | id == 0) continue;
else
{
for(int k = (id == 0) ? 1 : 0; k < twig[id].location.length; k++)
{
twig[id].branchRotate(k, twig[twig[id].baseIndex[0][j]].dTheta[twig[id].baseIndex[1][j]], twig[twig[id].baseIndex[0][j]].location[0]);
}
}
}
} // for(int id = 0; id < twig.length; id++)
time += dt;
if(time >= period) time -= period;
}
}
2. 页面左下角设有三个按钮,第一个实现从白天向夜晚的转变
这里就是在draw函数中改变了background的值。
3. 点击第二个按钮在屏幕上绘制自动绽放的五瓣花
花花代码如下:
void flower(float r, float c, float petalCount, float circleCount, float maxRad, float minRad, float frac, float rot) {
float rad = 0;
noStroke();
pushMatrix();
translate(width/2, height/2);
for (int j = 0; j< petalCount; j++)
for (float i = c; i <= circleCount; i = i + 1) {
float tt = i/circleCount;
float x = r*tt*cos( tt*rot + (2*PI*j)/petalCount-PI/2);
float y = r*tt*sin( tt*rot + (2*PI*j)/petalCount-PI/2);
if (i < frac*circleCount)
rad = map(i, 0, frac*circleCount, minRad, maxRad);
else
rad = map(i, frac*circleCount, circleCount, maxRad, minRad);
fill(lerpColor(color(255*t, 255, 0, 50), color(250*t + 205, 127*(1-t), 0, 100), i/circleCount));
ellipse(x+200, y-150, 2*rad, 2*rad);
ellipse(x-200, y-50, 2*rad, 2*rad);
}
popMatrix();
}
4. 点击第三个按钮实现“下雨”
关于下雨,其实就是一个最简单的粒子系统。关于粒子系统做简单描述。
“实现粒子系统,我们要先实现一个类,这个类用于表示单个粒子。粒子就是在屏 幕中移动的对象,它有位置、速度和加速度变量,有构造函数用于内部变量的初始 化,有display()函数用于绘制自身,还有update()函数用于更新位置。 ”
“在简单粒子的基础上,我们可以继续完善这个粒子类:可以在类中加 入applyForce()函数用于影响粒子的行为;可以加 入其他变量用于描述粒子的色彩和形状,或是用PImage对象绘制粒子。其中很关键的一点是我们需要设置粒子的生存期(lifespan)。 ”
因为如果不设置生命周期,那么粒子将会不断堆积然后会影响系统运行速度,因此我们需要新的粒子诞生也需要旧粒子消亡。
有了lifespan变量之后,我们还需要添加另一个函数,它返回一个布尔值,用于检 查粒子是否已经消亡。这个函数将在粒子系统类的实现中派上大用场。粒子系统的主 要职责是管理粒子的列表。这个函数的实现非常简单,我们只需要检查lifespan变 量是否小于0:如果小于0,就返回true;如果不小于0,就返回false。
代码如下:
class Particle {
PVector location;
PVector velocity;
PVector acceleration;
float lifespan;
Particle(PVector l) {
// The acceleration
acceleration = new PVector(0, 0.05);
// circel's x and y ==> range
velocity = new PVector(random(-1, 1), random(-2, 0));
// apawn's position
location = l.copy();
// the circle life time
lifespan = 255.0;
}
void run() {
update();
display();
}
void update() {
velocity.add(acceleration);
location.add(velocity);
lifespan-=1.0;
}
boolean isDead() {
if (lifespan <= 0) {
return true;
} else {
return false;
}
}
void display() {
// border
stroke(0, lifespan);
// border's weight
strokeWeight(1);
float r = random(0,255);
float g = random(0,255);
float b = random(0,255);
// random the circle's color
fill(125,209,240, lifespan);
// draw circle
ellipse(location.x, location.y-70, 5, 5);
ellipse(location.x-250, location.y-70, 5, 5);
ellipse(location.x+250, location.y-70, 5, 5);
}
}
// A class to describe a group of Particles
// An ArrayList is used to manage the list of Particles
class ParticleSystem {
ArrayList particles;
PVector origin;
ParticleSystem(PVector position) {
origin = position.copy();
particles = new ArrayList();
}
void addParticle() {
particles.add(new Particle(origin));
}
void run() {
for (int i = particles.size()-1; i >= 0; i--) {
Particle p = particles.get(i);
p.run();
if (p.isDead()) {
particles.remove(i);
}
}
}
}
推荐:
1.第一个推荐是用Processing制作一个「生态瓶」
创作者讲解的比较详细,并且界面具有美观性,也比较有趣味,主要运用了随机分布、向量、粒子系统和细胞自动机的相关知识。在一个【容器内】,你可以充当上帝的角色,对这一区域内的生态环境进行模拟。
2.第二个推荐是https://blog.csdn.net/qq_42123360/article/details/89893547
本篇博文灵感来源于新媒体艺术展览,展示了7中视觉效果展示图,分别是旋转万花筒、广袤星河、记忆碎片、方格眩晕、迷幻绳索、电子冲击、塌缩空间。并且可以给这些效果添加不同的滤镜。设计的动态效果是富有创意的。主要运用了噪声、角运动和随机的内容。
3.第三个推荐是《Gravity》——融入动画技术的交互应用
这个交互作品是一个太空模拟驾驶游戏的原型,使用openFrameworks配合XInput库,并使用C++编写。在游戏中,玩家使用XBOX One游戏手柄控制一架抽象为箭头的小飞机,在行星之间飞行,收集燃料以维持飞船的动力。玩家通过推动手柄的左摇杆来控制飞船的动力和航向,随着玩家推动摇杆,动力逐渐提升,并推动飞船加速或者减速。具体请移步链接。