互动编程习作——表现随机行为及牛顿运动学
《代码本色》第0章学习----随机
Perlin Noise,译作柏林噪声,是指Ken Perlin发明的噪声算法。Ken Perlin早在1983年就提出了Perlin noise,当时他正在参与制作迪士尼的动画电影《电子世界争霸战》,但是他不满足于当时计算机产生的那种非常不自然的纹理效果,因此提出了Perlin噪声。由于Perlin噪声的算法简单,被迅速应用到各种商业软件中。
Prelin由于出色的工作,获得了奥斯卡科技成果奖(奥斯卡是电影业的诺贝尔奖,靠着一个算法单枪匹马获得这个奖励,Perlin确实厉害)。
Processing内置了Perlin噪声算法的实现:noise()函数。
noise():得到一个无规律的小范围变化的随机值
返回指定坐标处的Perlin噪声值。Perlin噪声是随机序列发生器,产生比标准random()函数更自然,谐波的数字序列。
与随机random()相比函数,Perlin噪声定义在无限的n维空间中,其中每对坐标对应一个固定的半随机值(仅在程序的生命周期内固定)。结果值始终介于0.0和1.0之间。处理可以根据给定的坐标数计算1D,2D和3D噪声。
random(): 得到一个随机值,无规律的
下面是一个应用noise()的例子:
float my_num = 100;
void setup() {
size(400, 400);
}
void draw() {
background(255 * noise(my_num + 100)); //背景颜色随机的改变
stroke(255);
//noise()返回0到1之间的数字
//当将noise()乘以宽度时,我们得到一个介于0和width之间的数字
float x = noise(my_num) * width;
println(noise(my_num));
// 画一条垂直线
line(x, 0, x, height);
//我们将40加到my_num以避免获得完全相同的随机数
//我们之前调用了noise()函数
float y = noise(my_num + 40) * height;
// 画一条水平线
line(0, y, width, y);
my_num = my_num + 0.02;
}
使用noise()函数,修改自《代码本色》中的一个例子:一个随机游走的小球,颜色随时间变化
class Walker{
float x,y;
float tx,ty;
float r,b,g;
float tr,tb,tg;
Walker(){
tx=0;
ty=10000;
tr=0;
tb=1000;
tg=2000;
}
void step()
{
x=map(noise(tx),0,1,0, width);
y=map(noise(ty),0,1,0, height);
tx+=0.01;
ty+=0.01;
}
void c()//color
{
r=map(noise(tr),0,1,0,255);
b=map(noise(tb),0,1,0,255);
g=map(noise(tg),0,1,0,255);
tr+=0.01;
tb+=0.01;
tg+=0.01;
}
}
Walker w;
void setup()
{
size(480,480);
w=new Walker();
}
void draw()
{
w.step();
w.c();
fill(w.r,w.b,w.g);
ellipse(w.x,w.y,20,20);
}
《代码本色》第1章学习----向量
我们可以把向量当作两点之间的差异,也就是从一个点到另一个点所发生的移动。在编程时,对每一帧动画:新位置 = 当前位置在速度作用下的位置
如果速度是一个向量(两点之间的差异),那位置是否也是一个向量?位置也可以用一个向量表示,它代表原点与该位置的差异。
我们自己写个类来表示向量,那么可以这么开始:
class PVector {
float x;
float y;
PVector(float x_, float y_) {
x = x_;
y = y_;
}
}
在这里,PVector只是存储了两个变量(或者三维世界中的3个变量)的简单数据结构。
那么位置与速度就可以表示为:
PVector location = new PVector(100,100);
PVector velocity = new PVector(1,3.3);
既然已经有了位置和速度这两个向量对象,接下来就可以开始实现最基本的运动模拟:新位置 = 原位置 + 速度。
location = location + velocity; 将速度向量与位置向量相加
然而,在Processing语言中,加号(+)操作符是为原生数据类型(整数、浮点数等)预留的。Processing并不知道如何将两个PVector对象相加,就像它也不知道如何将两个PFont对象或PImage对象相加一样。但幸运的是,PVector类可以包含一些常用的数学操作函数。
下面是应用向量的例子,两个相互吸引运动的小球,在边界会转到另一边去:
class Mover
{
PVector location1,location2;
PVector v1,v2;
PVector acceleration1,acceleration2;
Mover()
{
location1=new PVector(random(width),random(height));
v1=new PVector(random(-2,2),random(-2,2));
acceleration1=PVector.random2D();
location2=new PVector(random(width),random(height));
v2=new PVector(random(-2,2),random(-2,2));
acceleration2=PVector.random2D();
}
void update()
{
// PVector mouse=new PVector(mouseX,mouseY);
PVector dir1=PVector.sub(location2,location1);
PVector dir2=PVector.sub(location1,location2);
dir1.normalize();
dir1.mult(0.5);
acceleration1=dir1;
v1.add(acceleration1);
v1.limit(10);
location1.add(v1);
//acceleration2=PVector.mult(dir1,-0.1);
dir2.normalize();
dir2.mult(0.5);
acceleration2=dir2;
v2.add(acceleration2);
v2.limit(10);
location2.add(v2);
}
void display()
{
stroke(0);
fill(175);
ellipse(location1.x,location1.y,16,16);
ellipse(location2.x,location2.y,16,16);
}
void checkEdges()
{
if(location1.x>width)
{
location1.x=0;
}
else if(location1.x<0)
{
location1.x=width;
}
if(location1.y>height)
{
location1.y=0;
}
else if(location1.y<0)
{
location1.y=height;
}
}
}
Mover mover;//s=new Mover[5];
void setup()
{
mover=new Mover();
size(480,480);
smooth();
//mover.acceleration.x=0.05;
//mover.acceleration.y=0.05;
}
void draw()
{
mover.update();
mover.checkEdges();
mover.display();
}
《代码本色》第2章学习----力
力和Processing的结合:将牛顿第二运动定律作为一个函数
牛顿第二运动定律是Processing编程中最重要的一个定律。该定律被表述为:力等于质量乘以加速度。
用公式表示为:F=M×A
它的另一种写法:A=F/M
加速度和力成正比,和质量成反比。这意味着如果你受一个推力作用产生运动,那么推力越大,运动越快(加速度越大);质量越大,运动越慢。
在Procesing中,质量是什么?假定在我们的像素世界里,所有对象的质量都等于1。根据F/1=F,就有:
A=F
物体的加速度等于力。这是一个好消息,因为加速度是控制物体运动的关键因素:位置由速度控制,而速度由加速度控制。加速度是一切运动的起因。根据上面的公式,现在力变成了运动的起因。
再来看看同时拥有位置(location)、速度(velocity)和加速度(acceleration)的Mover类:
class Mover{
PVector location;
PVector velocity;
PVector acceleration;
我们的目标是在对象上施加力,比如风力:
mover.applyForce(wind);
或者重力:
mover.applyForce(gravity);
这里的风力和重力都是PVector对象。根据牛顿第二运动定律,我们可以这么实现applyForce()函数:
void applyForce(PVector force){
acceleration=force;牛领第二定律最简单的实现方式
}
下面是模拟有初速度的小球的落体运动的例子:
Ball[] ball = new Ball[50]; //声明Ball类型的变量ball
Liquid water;
void setup() {
size(640, 640);
for (int i = 0; i < ball.length; i++) {
ball[i] = new Ball(random(0.5,6), random(0,width), random(0,height/2));
}
water = new Liquid(0, (height/3)*2, width, height/3, 0.01);
}
void draw() {
background(150, 180, 230);
water.display();
for (int i = 0; i < ball.length; i++) {
float m = ball[i].mass; //加速度与受到的力和小球质量有关,此处引入小球质量
PVector gravity = new PVector(0, 0.1*m); //重力与小球质量有关
PVector wind = new PVector(0.01, 0); //风力
if (ball[i].isInside(water)) {
ball[i].drag(water); //小球在区域内则受到阻力
}
ball[i].applyForce2(gravity); //小球受到重力之后的加速度
ball[i].applyForce2(wind);
ball[i].location();
ball[i].display();//绘制小球
ball[i].bounces();
}
}
class Ball {
float mass;//设定像素的质量
PVector xy; //原始圆的中心
PVector speedxy;
float speedtop;
PVector acceleration;
Ball(float m, float x, float y) {
mass = m;
xy = new PVector(x, y);
speedxy = new PVector(random(10,15), 15);
speedtop = 30;
acceleration = new PVector(0, 0);
}
void display() {
fill(255, 255, 100);
noStroke();
ellipse(xy.x, xy.y, 10*mass, 10*mass);//注意要乘以像素的mass
}
void applyForce2(PVector force) {
//1. acceleration = force; //此处若有两个力,重力与风力,最终只有3受到风阻力之后的加速度
//2. acceleration.add(force); //能够得到总加速度,但若小球质量不为1,加速度a = F/m
//3. 引入新变量f赋予force的值,除以质量mass,得到最终加速度。此处用f为了不改变force的值
PVector f = force.get();
f.div(mass);
acceleration.add(f);
}
void location() {
speedxy.add(acceleration);//更新速度
speedxy.limit(speedtop);//速度限制,限制为10
xy.add(speedxy); //更新位置
acceleration.mult(0); //每次加速度都要清零,因为每次力引起的加速度都会重复计算,若不清零,加速度会不断叠加
}
void bounces() {
if ( xy.x < 0 ) {
xy.x = 0;
speedxy.x *= -1;
} else if ( xy.x > width ) {
xy.x = width;
speedxy.x *= -1;
}
if ( xy.y < 0 ) {
xy.y = 0;
speedxy.y *= -1;
} else if ( xy.y > height ) {
xy.y = height;
speedxy.y *= -1;
}
}
boolean isInside(Liquid w) {
if (w.lx<xy.x && xy.x<w.lx+w.lwidth && w.ly<xy.y && xy.y<w.ly+w.lheight) {
return true;
} else {
return false;
}
}
void drag(Liquid w) {
float cd = 0.002; //假设阻力系数
float v = speedxy.mag(); //速率
PVector drag = speedxy.get(); //将速度向量长度单位化
drag.normalize(); //将速度向量长度单位化
drag.mult(-1); //将速度向量方向反向
drag.mult(v * v * cd); //阻力
applyForce2(drag); //阻力给予小球的加速度
}
}
class Liquid {
float lx, ly, lwidth, lheight;
float Cd; //设定一个阻力系数
Liquid(float lx_, float ly_, float lwidth_, float lheight_, float Cd_){
lx = lx_;
ly = ly_;
lwidth = lwidth_;
lheight = lheight_;
Cd = Cd_;
}
void display() {
noStroke();
fill(150, 200, 255);
rect(lx, ly, lwidth, lheight);
}
}
《代码本色》第3章学习----振荡
钟摆模型是摆锤悬挂在轴点上的运动,为了简化模型,我们在此处建设钟摆运动是在二维空间之中。由于摆锤运动是以轴点为圆心做运动,所以,引入角度,角速度,角加速度等几个概念来表达摆锤的运动。
为了绘制摆锤的运动,我们需要描述的是摆锤和绳子两个对象。每个对象所需的信息:摆锤:圆心、半径、摆锤的角度、角速度、角加速度;绳子:绳子的长度、两端的端点。
然后需要考虑的就是如何更新钟摆每一时刻的位置,如何来表达摆锤的运动呢?
摆锤的长度是固定的,唯一变化的部分只是绳子的角度,我们可以用角速度与角加速度模拟钟摆的运动。小球只受到两个力,一是小球自身的重力,二是绳子对小球的拉力。因为合力和绳子互相垂直,通过绘图可知,最终angularAcceleration = gravity * sin(angle),与小球质量无关。但实际上,钟摆摆臂的长度对加速度的影响很大,摆臂越长,加速度越小。所以,角加速度angularAcceleration = -1 * gravity * sin(angle) / string。 而为了模拟现实世界,还会受到摩擦力和空气阻力,可以设置一种衰减方式,设置变量damping。
下面是实现钟摆代码:
Pendulum[] ball = new Pendulum[18];
void setup() {
size(1280, 640);
for (int i=0; i<ball.length; i++) {
ball[i] = new Pendulum( 30*i, PI/4);
}
}
void draw() {
background(0);
for (int i=0; i<ball.length; i++) {
ball[i].update();
ball[i].display();
}
}
class Pendulum { //创建摆锤的类
//小球位置、角度、角速度和加速度
PVector location;
float angularAcceleration;
float angularVelocity;
float angle;
//绳子与端点
float string; //绳子长度
PVector origin; //绳子端点
//减震幅度
float damping;
Pendulum(float string_, float angle_) {
location = new PVector(0, 0); //先任意假设小球位置,之后会更变
angle = angle_;
angularVelocity = 0;
angularAcceleration = 0;
string = string_;
origin = new PVector(width/2, 50);
damping = 0.995;
}
void update() {
float gravity = 0.98; //重力常量
angularAcceleration = -1 * sin(angle) * (gravity/string); //在实际情况中,摆臂越长,加速度越小
angularVelocity += angularAcceleration;
angle += angularVelocity;
angularAcceleration *= damping; //减震
}
void display() {
location = new PVector(string * sin(angle), string * cos(angle)); //极坐标可知小球圆心与绳子端点的距离
location.add(origin); //将小球圆心更新到绝对位置
stroke(255,255,0);
fill(255,255,0);
line(origin.x, origin.y, location.x, location.y);
ellipse(location.x, location.y, 30, 30);
}
}
《代码本色》第4章学习----粒子系统
粒子系统通常用来模拟一些自然现象,例如火焰燃烧、烟花爆炸、落叶、云的流动等一系列抽象的视觉效果,通过模拟粒子的物理运动来还原这些模糊现象。
粒子系统中的粒子可以是任何想要并且写得出的形状,我们首先就画一颗小星星。 然后,需要来控制单个粒子的运动。单个粒子的运动与上一章摆锤中运用到的位置、速度、加速度等因素都相关,可以通过更改这些因素控制星星的运动。利用数组管理粒子对象,是一个很好的方法。将add()函数放在void draw()函数之中,因为void draw()函数是在不断循环的,星星就能不断生成。通过ArrayList类之中提供的remove()函数消除粒子对象。最后完成粒子系统类——用一个类描述由例子对象组成的系统,至此星星粒子系统就完成了。
下面是代码:
import java.util.Iterator; //导入迭代器
ArrayList<StarMoveSystem> systems;
void setup() {
size(500, 700);
systems = new ArrayList<StarMoveSystem>();
}
void draw() {
background(0);
for (StarMoveSystem s : systems) { //for循环
s.run();
s.addStarMove();
}
}
void mousePressed() {
systems.add(new StarMoveSystem(new PVector(mouseX, mouseY)));
}
class Star { //自定义声明对象Star
//公共变量
PVector star;
//自定义属性
Star(PVector starLocation) { //代入星星的坐标信息
star = starLocation.get();
}
//自定义功能,此处定义display()一种功能
void display() {
beginShape();
vertex(star.x, star.y);
vertex(star.x + 2, star.y + 8);
vertex(star.x + 10, star.y + 8);
vertex(star.x + 4, star.y + 12);
vertex(star.x + 7, star.y + 20);
vertex(star.x, star.y + 15);
vertex(star.x - 7, star.y + 20);
vertex(star.x - 4, star.y + 12);
vertex(star.x - 10, star.y + 8);
vertex(star.x - 2, star.y + 8);
endShape(CLOSE);
}
}
class StarMove{ //自定义声明对象StarMove
//1.公共变量:位置、速度、加速度
PVector location;
PVector velocity;
PVector acceleration;
float lifespan;
//2.初始化变量,自定义各项属性
StarMove(PVector l) { //其中坐标位置需要输入
location = l.get();
acceleration = new PVector(0, 0.05);
velocity = new PVector(random(-1, 1), random(-2, 0));
lifespan = 255.0;
}
void run() { //用run()功能统筹管理update()与display()功能
update();
display();
}
void update() { //更新坐标位置
velocity.add(acceleration);
location.add(velocity);
lifespan -= 1.0;
}
void display() { //绘制星星
noStroke();
fill(255, 255, 0);
Star star = new Star(location); //新建Star类型的对象star
star.display(); //绘制小星星
}
Boolean isDead(){
if(lifespan < 0.0){
return true;
}else{
return false;
}
}
}
class StarMoveSystem {
ArrayList<StarMove> slist;
PVector originLocation;
StarMoveSystem(PVector location) {
originLocation = location.get();
slist = new ArrayList<StarMove>();
}
void addStarMove() {
slist.add(new StarMove(originLocation));
}
void run() {
Iterator<StarMove> it = slist.iterator();
while (it.hasNext()) {
StarMove s = it.next();
s.run();
if (s.isDead()) {
it.remove();
}
}
}
}