互动编程习作——表现随机行为及牛顿运动学

《代码本色》第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();
      }
    }
  }
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值