processing互動編程習(xí)作——作業(yè)博文

第一部分:0~2章的針對習(xí)作

0~2章的主要內(nèi)容分別為隨機數(shù)生成、向量的使用和牛頓運動力學(xué)的簡單模擬冻璃。

第0章針對習(xí)作:隨機點線生成

其可以當(dāng)作是燈光评肆,或者是雨點,或者是你認(rèn)為的任何東西陕赃。表現(xiàn)圖如下:


程序0.gif

在屏幕上不斷出現(xiàn)這樣的指向四周的射線卵蛉,再不斷消失。此外么库,鼠標(biāo)移動到最左端傻丝,色彩的變化就會比較緩慢,而移動到最右端诉儒,色彩將會比較快速地切換葡缰。但不論如何,色彩總是平滑變化的。
隨機效果應(yīng)用在:
1泛释、射線起點(小圓圈)的位置——高斯隨機數(shù)滤愕。
2、射線的方向——蒙特卡洛算法配合拋物線生成的隨機方向怜校。
3间影、射線的長度和線寬——自帶的偽隨機數(shù)函數(shù)。
4茄茁、射線的色彩——泊林噪聲帶來的平滑漸變效果魂贬,保證了屏幕上總是相近色居多。由于我太喜歡這個泊林噪聲的平滑色彩了裙顽,所以在后面的程序里都用了這種色彩產(chǎn)生方式付燥。相近色真的非常好看。

以下是代碼文本锦庸,注釋非常詳細(xì)机蔗。

void setup(){
  size(520,520);
  fill(0,0,0,5);
  rect(0,0,width,height);
  frameRate(25);
}

float t=0;
float delt=0.001;

void draw(){  
  fill(0,0,0,2);
  rect(0,0,width,height);        //清屏
  translate(width/2,height/2);    //原點移動到畫布中心
  
  PVector startPoint=new PVector(0,0);  //起點向量
  PVector endPoint=new PVector();    //終點向量
  PVector direct=new PVector();      //方向向量
  float lengthOfline=0;              //長度
  float widthOfline=2;                //寬度
  color col;                        //顏色
  
  float gus;                  //高斯隨機數(shù)
  //確定起點
  gus=(float)randomGaussian();
  startPoint.x=map(gus,-3,3,-width/2,+width/2);
  gus=(float)randomGaussian();
  startPoint.y=map(gus,-3,3,-height/2,+height/2);
  
  int xx=1;              //象限數(shù)
  //確定起點象限
  if(startPoint.x>=0&&startPoint.y>=0)xx=1;
  else if(startPoint.x>=0&&startPoint.y<=0)xx=4;
  else if(startPoint.x<=0&&startPoint.y<=0)xx=3;
  else if(startPoint.x<=0&&startPoint.y>=0)xx=2;
  
  //確定方向
  direct=getDirect(xx);
  
  //確定長度和線寬
  lengthOfline=random(1,300);
  widthOfline=random(1,8);
  
  //確定終點
  direct.mult(lengthOfline);
  endPoint.set(startPoint);
  endPoint.add(direct);
  
  //確定顏色
  float r,g,b;
  r=noise(t);
  g=noise(t+200);
  b=noise(t+400);          //rgb值錯開,防止全是白色
  
  r=map(r,0,1,100,255);    //映射到一個比較亮的值
  g=map(g,0,1,100,255);
  b=map(b,0,1,100,255);
  
  col=color(r,g,b);
  
  stroke(col,175);
  strokeWeight(widthOfline);
  //畫線
  line(startPoint.x,startPoint.y,endPoint.x,endPoint.y);
  
  fill(col,110);
  noStroke();
  //畫圈
  ellipse(startPoint.x,startPoint.y,widthOfline+10,widthOfline+10);
  
  t+=delt;
  delt=0.0001*mouseX;
}

PVector getDirect(int xx){
  PVector direct=new PVector();
  float angle=0;
  while(true){
    //蒙特卡洛算法甘萧,產(chǎn)生0~2pi的數(shù)字
    float r1,P,r2;
    r1=random(0,1);
    P=-2*(r1-0.5)*(r1-0.5)+0.55;  //概率設(shè)置為拋物線
    if(xx==1){                  //第一象限萝嘁,需要的角度值為0~PI/2,其它象限以此類推
      r1=map(r1,0,1,0,PI/2);
    }
    else if(xx==2){
      r1=map(r1,0,1,PI/2,PI);
    }
    else if(xx==3){
      r1=map(r1,0,1,PI,3*(PI/2));
    }
    else if(xx==4){
      r1=map(r1,0,1,3*(PI/2),2*PI);
    }
    r2=random(0,0.55);        //概率的最大值就是0.55
    if(r2<P){
      angle=r1;            //角度記為angle
      break;
    }
  }
  //將角度換算成向量
  direct.x=cos(angle);        //方向向量扬卷,模為1
  direct.y=sin(angle);
  return direct;
}

其中牙言,行8~9是泊林噪聲的參數(shù),每次遞增值與鼠標(biāo)位置相關(guān)(行72)怪得。

高斯隨機數(shù)的范圍其實是負(fù)無窮到正無窮咱枉,但是一般都集中在0(均值)附近。所以映射時只要考慮0左右的一個不大的區(qū)間即可(具體區(qū)間大小與方差有關(guān)徒恋,方差越大蚕断,區(qū)間越大。但將近98%的數(shù)值都集中在均值左右3個標(biāo)準(zhǔn)差的區(qū)間內(nèi)入挣,證明需要概率論的知識)亿乳,將它映射到畫布尺寸,即可作為位置径筏。(行23~28)葛假。

確定方向使用了蒙特卡洛算法。根據(jù)起點的象限不同滋恬,其最終得到的方向范圍也不同聊训。因此在行83~94,對結(jié)果進(jìn)行了映射恢氯。

蒙特卡洛算法是一種隨機數(shù)生成算法带斑,其算法為:先生成一個偽隨機數(shù)r1鼓寺,根據(jù)自定的概率函數(shù)來計算r1的概率P。再生成一個偽隨機數(shù)r2勋磕,若r2小于P侄刽,則認(rèn)為取到r1,否則重新執(zhí)行算法朋凉。其優(yōu)點在于,P的函數(shù)可以自行任意地定義醋安。代碼中選擇了0~1的一個拋物線來作為角度的概率函數(shù)杂彭。

蒙特卡洛算法得到的是一個0~2PI的數(shù)字,即角度吓揪。它相當(dāng)于極坐標(biāo)下的theta量亲怠。用向量表示這個角度,則需要將極坐標(biāo)轉(zhuǎn)換成二維坐標(biāo)柠辞,模長為一团秽,即r為1。(行102~103)

得到起點和方向向量之后叭首,終點向量則根據(jù)公式:終點=起點+方向*步長 得到习勤。最后以起點和終點坐標(biāo)畫線,并在起點位置畫個圓焙格,即可图毕。

第1章針對習(xí)作:向量運算的簡單可視化。

支持加眷唉、減予颤、數(shù)乘和數(shù)量積。表現(xiàn)如下:


程序1.1冬阳,求和.gif
程序1.2蛤虐,求差.gif
程序1.3,數(shù)乘.gif
程序1.4肝陪,內(nèi)積.gif

分別是求和驳庭、求差、數(shù)乘和數(shù)量積见坑。
求和——鼠標(biāo)點擊至少兩個向量后嚷掠,按S計算,畫出所有向量的和向量荞驴。
求差——鼠標(biāo)點擊至少兩個向量后不皆,按M計算,以第一個向量為起點熊楼,依次減去后來畫出的向量霹娄。如果只有兩個向量能犯,則追加畫出三角形法則,紅點為結(jié)果的終點犬耻。
數(shù)乘——鼠標(biāo)點擊至少兩個向量后踩晶,按U計算,取第二個向量的模除十之后枕磁,去對第一個向量進(jìn)行數(shù)乘渡蜻,畫出結(jié)果向量。因此计济,不會處理符號茸苇,即第一個向量永遠(yuǎn)是被延長的。
數(shù)量積——鼠標(biāo)點擊至少兩個向量后沦寂,按D計算学密,將計算前兩個向量的數(shù)量積,并以此為半徑传藏,畫一個原點在中心的圓腻暮。

由于叉乘的結(jié)果向量是垂直于兩個運算向量的,所以需要三維畫布毯侦,就沒做哭靖。

代碼文本如下:

void setup(){
  size(600,600);
}
int existpoints=0;      //點的數(shù)量
ArrayList<PVector> points=new ArrayList<PVector>();  //點列表
void draw(){
  fill(0);
  rect(0,0,width,height);
  translate(width/2,height/2);
  
  float mousx,mousy;
  mousx=map(mouseX,0,width-1,-width/2,width/2);    //mouseX永遠(yuǎn)是以左上為原點的,需要重新映射
  mousy=map(mouseY,0,height-1,-height/2,height/2);
  
  if(existpoints==0){
    stroke(255,255,255);
    strokeWeight(3);
    line(0,0,mousx,mousy);    //跟隨鼠標(biāo)畫線
  }
  
  if(existpoints>=1){          //畫出列表里所有向量
    //畫出之前的所有向量
    PVector ed;
    ed=new PVector(0,0);
    for(int i=0;i<points.size();i++){
      ed.set(points.get(i));
      stroke(255,255,255);
      strokeWeight(3);
      line(0,0,ed.x,ed.y);
    }
    line(0,0,mousx,mousy);    //跟隨鼠標(biāo)畫線
  }
}

void mouseClicked() {
    existpoints++;          //鼠標(biāo)點擊就將這個點記入隊列
    PVector firstPoint=new PVector();
    firstPoint.x=map(mouseX,0,width-1,-width/2,width/2);
    firstPoint.y=map(mouseY,0,height-1,-height/2,height/2);
    points.add(firstPoint);
}

void keyPressed() {
  if (key == 's'||key == 'S') {      //求和
    if(existpoints>=2&&looping){
      //至少有兩個向量侈离,且當(dāng)前要在循環(huán)中
      PVector sumPts=new PVector(0,0);
      for(int i=0;i<points.size();i++){
        sumPts.add(points.get(i));    //累加
      }
      //清屏款青,畫出和
      background(0);
      //畫出之前的所有向量
      PVector ed;
      ed=new PVector(0,0);
      for(int i=0;i<points.size();i++){
        ed.set(points.get(i));
        stroke(255,255,255);
        strokeWeight(3);
        line(0,0,ed.x,ed.y);
      }
      
      stroke(55,25,215);            //畫結(jié)果
      strokeWeight(4);
      line(0,0,sumPts.x,sumPts.y);
      
      noLoop();    //停掉循環(huán)
    }
  }
  
  if(key == 'm'||key == 'M'){
    //求差,順次兩兩相減
    if(existpoints>=2&&looping){
      //至少有兩個向量霍狰,且當(dāng)前要在循環(huán)中
      PVector minusPts=new PVector(0,0);
      minusPts.set(points.get(0));
      for(int i=1;i<points.size();i++){
        minusPts.sub(points.get(i));
      }
      //清屏抡草,畫出差
      background(0);
      //畫出之前的所有向量
      PVector ed;
      ed=new PVector(0,0);
      for(int i=0;i<points.size();i++){
        ed.set(points.get(i));
        stroke(255,255,255);
        strokeWeight(3);
        line(0,0,ed.x,ed.y);
      }
      
      stroke(55,25,215);
      strokeWeight(4);
      line(0,0,minusPts.x,minusPts.y);
      
      //如果只有兩個向量相減,還會畫出三角形法則圖蔗坯,向量終點畫一個小紅點
      if(existpoints==2){
        stroke(55,215,215);
        strokeWeight(3);
        line(points.get(1).x,points.get(1).y,points.get(0).x,points.get(0).y);
        //終點是被減數(shù)的位置
        fill(235,25,35);
        noStroke();
        ellipse(points.get(0).x,points.get(0).y,8,8);
      }
      noLoop();    //停掉循環(huán)
    }
  }
  
  if(key == 'u'||key == 'U'){
    //數(shù)乘康震,只處理第一和第二向量。第一個向量為目標(biāo)向量宾濒,第二個向量取模/10作為倍數(shù)腿短。
    //不處理符號
    if(existpoints>=2&&looping){
      //至少有兩個向量,且當(dāng)前要在循環(huán)中
      PVector multsPts=new PVector(0,0);
      multsPts.set(points.get(0));      //取第一個向量
      float lengths=points.get(1).mag()/10;    //取第二個向量的模/10
      
      multsPts.mult(lengths);    //數(shù)乘
      
      //清屏绘梦,畫出數(shù)乘
      background(0);
      //先畫結(jié)果
      stroke(55,25,215);
      strokeWeight(5);
      line(0,0,multsPts.x,multsPts.y);
      //再畫原向量橘忱。第一向量必定被結(jié)果覆蓋,所以后畫
      stroke(255,255,255);
      strokeWeight(3);
      line(0,0,points.get(0).x,points.get(0).y);
      stroke(25,205,85);
      line(0,0,points.get(1).x,points.get(1).y);
      noLoop();    //停掉循環(huán)
    }
  }
  
  if(key == 'd'||key == 'D'){
    //點乘卸奉,只計算前兩個向量的內(nèi)積钝诚。
    //結(jié)果是標(biāo)量,用紅色圓圈表示榄棵,其半徑為內(nèi)積結(jié)果
    if(existpoints>=2&&looping){
      //至少有兩個向量凝颇,且當(dāng)前要在循環(huán)中
      float dotResult=0;
      dotResult=PVector.dot(points.get(0),points.get(1));  //數(shù)量積
      
      //清屏潘拱,畫出內(nèi)積
      background(0);
      //畫出前兩個向量
      stroke(255,25,255);
      strokeWeight(3);
      line(0,0,points.get(0).x,points.get(0).y);
      stroke(25,205,85);
      line(0,0,points.get(1).x,points.get(1).y);
      
      //畫出內(nèi)積
      stroke(200,35,35);
      noFill();
      ellipse(0,0,dotResult,dotResult);
      noLoop();    //停掉循環(huán)
    }
  }
  
  if(key == 'c'||key == 'C'){
    //清空隊列,開啟循環(huán)拧略,即初始化
    if(!looping){
      points.clear();
      existpoints=0;
      loop();
    }
  }
}

由于原點設(shè)置在了畫布中心芦岂,而mouseX和mouseY都是以左上角為原點來計數(shù)的,所有需要一次映射轉(zhuǎn)換垫蛆。

其余的禽最,代碼中有詳盡注釋。

第2章針對習(xí)作:牛頓力學(xué)的簡單使用

做了個類似于粒子的效果袱饭。所有小球都有隨機的大小和質(zhì)量弛随,都受到鼠標(biāo)的引力吸引,同時可以增加左右方向的水平風(fēng)力宁赤,開啟空氣阻力和重力。


程序2.0栓票,默認(rèn)狀態(tài).gif

程序2.1决左,重力,很多球都掉落了走贪,少量球被吸引.gif

程序2.2佛猛,空氣阻力作用下,小球快速聚集坠狡。再不斷加水平向右的風(fēng)力把它吹散.gif

操作如下:
S——風(fēng)停继找。
A、D——向左吹風(fēng)/向右吹風(fēng)的風(fēng)力增加逃沿。
G——開啟/關(guān)閉重力婴渡。
F——開啟/關(guān)閉空氣阻力。

同樣的凯亮,這樣的相近色也是由泊林噪聲隨機生成的边臼,生成方法與之前的一樣。另外假消,小圓不加邊框是來自同學(xué)的意見柠并,表示這種朦朧感更適合淺色系。

代碼文本如下:
主程序:

ArrayList<mover> movs=new ArrayList<mover>();  //列表
int num=60;      //mover數(shù)量
PVector windForce;    //風(fēng)力向量
boolean isgravity=false;    //是否開啟重力和空氣阻力
boolean isfAir=false;
void setup(){
  size(1200,800);
  float tt=1;        //泊林噪聲參數(shù)富拗,在構(gòu)造函數(shù)中用到
  for(int i=0;i<num;i++){
    movs.add(new mover(tt));          //生成小球們
    tt+=0.045;
  }
  windForce=new PVector(0,0);          //初始化風(fēng)力
}

void draw(){
  fill(255,5);
  rect(0,0,width,height);
  
  for(int i=0;i<num;i++){            
    movs.get(i).update(windForce,isgravity,isfAir);  //更新位置
    movs.get(i).Bounds();        //防止飛得太遠(yuǎn)臼予,吸不回來
    movs.get(i).display();        //畫出自己
  }
}

void keyPressed(){
  if(key == 's'||key == 'S'){
    //風(fēng)停
    windForce.set(0,0);
  }
  if(key == 'a'||key == 'A'){
    //向左吹風(fēng)風(fēng)力增加
    PVector addLeftForce=new PVector(-0.02,0);
    windForce.add(addLeftForce);
  }
  if(key == 'd'||key == 'D'){
    //向右吹風(fēng)風(fēng)力增加
    PVector addRightForce=new PVector(0.02,0);
    windForce.add(addRightForce);
  }
  if(key == 'g'||key == 'G'){
    isgravity=!isgravity;      //啟動重力模仿。重力比較小啃沪,使得能夠吸回大部分小球
  }
  if(key == 'f'||key == 'F'){
    isfAir=!isfAir;            //啟動空氣阻力模仿粘拾。
  }
}

Mover類:

class mover{
  PVector location;    //位置
  PVector speed;      //速度
  PVector acceleration;  //加速度
  float mass;    //質(zhì)量越大,半徑越大创千,最大速度越小
  float topspeed;  //最大速度
  float radius;    //半徑
  color col;      //色彩
  
  public mover(float tt){    //構(gòu)造函數(shù)
    location=new PVector(random(0,width),random(0,height));  //隨機位置
    speed=new PVector(0,0);      //無速度和加速度
    acceleration=new PVector(0,0);
    mass=random(1,5);      //隨機質(zhì)量
    topspeed=42.5/(mass);  //根據(jù)質(zhì)量算最大速度和半徑
    radius=mass*8;
    float r,g,b;          //泊林色彩
    r=noise(tt);
    g=noise(tt+300);
    b=noise(tt+900);
    r=map(r,0,1,100,255);
    g=map(g,0,1,100,255);
    b=map(b,0,1,100,255);
    col=color(r,g,b);
  }
  
  void update(PVector windForce, boolean isgravity, boolean isfAir){
    //傳入風(fēng)力半哟、重力酬滤、空氣阻力,自身永遠(yuǎn)受到向鼠標(biāo)的牽引力
    PVector Yin=new PVector(mouseX,mouseY);    //求牽引力
    Yin.sub(location);        //從小球指向鼠標(biāo)
    float dist=Yin.mag();    //到鼠標(biāo)的距離
    Yin.normalize();      //引力的方向寓涨,規(guī)格化即可
    float yinForce=mass*20/(dist);  //引力大小盯串,質(zhì)量乘積并除以距離平方。
    //但是距離平方效果太差戒良,故改為只除一個距離
    Yin.mult(yinForce);    //數(shù)乘大小体捏,得到引力向量
    
    //算重力和空氣阻力
    PVector gravity,fAir;
    gravity=new PVector(0,0);
    fAir=new PVector(0,0);
    if(isgravity){      //啟動重力
      //重力豎直向下,方向為Y軸正方向糯崎,大小為mg几缭,g取9.8
      gravity.set(0,1);
      float gf=mass*9.8*0.003;    //為了更好的效果,乘以一個系數(shù)0.003
      gravity.mult(gf);
    }
    if(isfAir){
      //阻力方向等于速度方向的相反方向沃呢。大小為系數(shù)*速度平方
      fAir.set(speed);
      float af=speed.mag();    //速度的大小
      fAir.normalize();
      af=af*af;          //平方
      af=af*0.015;      //乘系數(shù)年栓,得到大小
      fAir.mult(-af);    //數(shù)乘,必須是負(fù)的
    }
    
    PVector Forces=new PVector(0,0);    //合力
    Forces.set(Yin);
    Forces.add(windForce);
    Forces.add(gravity);
    Forces.add(fAir);          //合力
    
    float a=Forces.mag()/mass;    //加速度大小薄霜,除以質(zhì)量某抓,方向即合力方向
    acceleration.set(Forces.normalize().mult(a));
    
    //移動
    speed.add(acceleration);    //速度改變
    speed.limit(topspeed);    //速度限制
    location.add(speed);      //位置改變
  }
  void Bounds(){      //防止飛出邊界太遠(yuǎn)
    if(location.x<-100)location.x=-100;
    if(location.y<-100)location.y=-100;
    if(location.x>width+100)location.x=width+100;
    if(location.y>height+100)location.y=height+100;
  }
  void display(){    //畫球
    noStroke();
    //strokeWeight(1);
    fill(col,85);
    ellipse(location.x,location.y,radius,radius);
  }
}

基本思路倒過來推比較容易理解:
畫出小球
——需要得到小球的位置
——位置由速度改變,需要得到小球的速度
——速度由加速度改變惰瓜,需要得到小球的加速度
——加速度由小球那個時刻受到的合力改變否副,需要得到小球的合力
——合力矢量由分力矢量相加得到,需要得到小球的分力
——分力矢量有大小方向崎坊,這就是我們需要模擬的東西备禀,剩下的交給小球自己就好。

看起來很麻煩奈揍,但實際上曲尸,我們只需要制定一套規(guī)則,除了分力之外男翰,所有變量都會自行變化队腐。規(guī)則可以自己隨意定,比如讓小球永遠(yuǎn)向速度反方向移動奏篙,等等柴淘。而牛頓制定的規(guī)則如下(以下無特殊說明,均為矢量運算):

位置=上一幀位置+速度秘通;
速度=上一幀速度+加速度为严;
加速度=合力/質(zhì)量(標(biāo)量);
合力=分力累加肺稀。

在mover類中第股,行67、行69就做了位置上的運算话原。行57~61在計算合力夕吻,行63~64在計算加速度诲锹,其余要么在控制小球的性狀(防止越出邊界或者畫出小球),要么就是在按照一定規(guī)則計算分力涉馅。

所有需要用到“上一幀”的變量都必須存儲归园,這里是位置和速度。當(dāng)然有算法可以只存儲一個稚矿,那是比較高級的能發(fā)論文的算法庸诱。這些量在計算時總是調(diào)用add方法,即累加晤揣。而其它量只需要set桥爽,即直接賦值即可。

剩下的就只要指定分力即可昧识。這里安置了重力钠四、風(fēng)力、空氣阻力和引力這四個力跪楞。風(fēng)力是與小球本身的運動情況和性狀無關(guān)的缀去,所以沒有放在mover類中,其它力均與小球自身有關(guān)——重力只與質(zhì)量有關(guān)习霹,空氣阻力與速度有關(guān)很泊,引力與距離和質(zhì)量有關(guān)崖瞭。

根據(jù)力學(xué)知識,確定好小球受到的這些力的方向和大小雕拼,記作向量伪阶,就可以了煞檩。行29~55就在做這個工作。

為了最后表現(xiàn)效果比較好看栅贴,我微調(diào)了幾個力的運算規(guī)則斟湃,所以可能不夠真實,但是會有受到那個力的趨勢檐薯。

第一部分:3~4章的針對習(xí)作

3~4章的主要內(nèi)容分別為:旋轉(zhuǎn)凝赛、三角函數(shù)、振蕩坛缕、彈力和極坐標(biāo)系相關(guān)知識墓猎,以及粒子系統(tǒng)。

第3章針對習(xí)作:三角函數(shù)構(gòu)成的粒子流動

改編自以前的程序赚楚。
會有許多粒子圍繞鼠標(biāo)位置毙沾,按一定規(guī)則形成各種波形,并“流動”宠页。視覺效果很好左胞。


程序3.0.gif

一些操作方法及功能:
S——改變波形的規(guī)則寇仓,使其軌道變?yōu)闄E圓,再次點擊則換回振蕩波形烤宙。
T——增加對形狀影響重大的參數(shù)TT遍烦,每次加0.5。
G——減少參數(shù)TT门烂,每次減0.5乳愉。
滾輪——上下滾動,調(diào)整圖形大小屯远。
單擊——改變插值規(guī)則蔓姚,立刻顯示當(dāng)前插值的目標(biāo)點形成的形狀。

初始時慨丐,重要形狀參數(shù)TT為10坡脐,單擊后可見其完整波形如圖:


TT是10時的形狀

其方程為:
x

y

代碼中使用lerp函數(shù),向這個軌跡插值房揭,每次插值0.5%备闲,由于角速度變化的不同,形成了上面那張圖的效果捅暴。單擊后即每次插值100%恬砂,直接移動到這個軌跡上,形成確定的圖案蓬痒。

按下S泻骤,改變方程中TT的運算規(guī)則,變?yōu)?div id="7jrgkiy" class="image-package">
相位變化

這個軌跡是一個橢圓:


橢圓

這是鼠標(biāo)移動時的結(jié)果梧奢。進(jìn)行插值狱掂,會形成類似星云的效果:
程序3.1.gif

改變TT,得到不同的圖案:


不斷變TT

代碼文本如下:

int num = 400;      //點的數(shù)量
float mts = PI/24;    //最大角速度亲轨,影響每個點運動的快慢
int r = 400;        //外圍圓半徑

int rdu = 4;      //每個小圓的半徑
float TT=10;        //形狀參數(shù)

PVector v[]=new PVector[num];  //位置趋惨,而非速度
boolean mv = true;          //顯示函數(shù)形狀,把插值百分比變成1即可
boolean mo = true;          //更改模式惦蚊,橢圓為+-器虾,曲線為*/
color c[] = new color[num];        //每個點的顏色

float mtheta[] = new float[num];    //弧度
float dtheta[] = new float[num];    //每個點走的快慢
float easing[] = new float[num];    //插值百分比。都一樣的話蹦锋,結(jié)果會很整齊曾撤,否則會有點雜亂。

int rdt[] = new int[num];          //偏離半徑的大小

void setup() {
  colorMode(RGB, 255, 255, 255);
  float r, g, b;
  float t=159;
  size(800, 1000);
  for (int i =0; i<num; i++) {
    r=noise(t);
    g=noise(t+300);
    b=noise(t+900);
    r=map(r, 0, 1, 100, 255);
    g=map(g, 0, 1, 100, 255);
    b=map(b, 0, 1, 100, 255);
    c[i] = color(r, g, b);
    v[i] = new PVector(random(width), random(height));
    dtheta[i] = random(mts);
    mtheta[i] = round(0)/180*PI;    //初始角度轉(zhuǎn)弧度
    rdt[i] = 0;        //不偏離軌道晕粪,得到一條整齊的細(xì)線

    easing[i] =0.005;
    t+=0.05;
  }
}

void draw() {
  fill(25, 25, 25, 5);
  rect(0, 0, width, height);
  pushMatrix();
  noStroke();
  if (mo) {          //周期變化模式
    for (int i = 0; i<num; i++) {
      mtheta[i] += dtheta[i];
      v[i].lerp(mouseX+cos(mtheta[i])*(rdt[i]+r), mouseY+cos(mtheta[i]/TT)*(rdt[i]+r), 0, easing[i]);    // /為豎著的挤悉,*為橫著的,且*的時候變化過于劇烈
      //v[i].set(mouseX+cos(mtheta[i])*(rdt[i]+r), mouseY+sin(mtheta[i]/TT)*(rdt[i]+r));
      fill(c[i], 70);
      ellipse(v[i].x, v[i].y, rdu, rdu);
    }
  }
  if (!mo) {        //橢圓軌道模式
    for (int i = 0; i<num; i++) {
      mtheta[i] += dtheta[i];
      v[i].lerp(mouseX+cos(mtheta[i])*(rdt[i]+r), mouseY+sin(mtheta[i]+TT)*(rdt[i]+r), 0, easing[i]);  //橢圓軌道為基,TT=0的時候是圓
      fill(c[i], 70);
      ellipse(v[i].x, v[i].y, rdu, rdu);
    }
  }

  popMatrix();
}
void mousePressed() {
  if (mv) {
    for (int i=0; i<num; i++) {
      easing[i]=1;
    }
    mv=!mv;
  } else {
    for (int i=0; i<num; i++) {
      easing[i]= 0.005;
    }
    mv=!mv;
  }
}
void keyPressed() {
  if (key == 's'||key == 'S') {
    mo =!mo;
  }
  if (key=='t'||key=='T') {
    TT+=0.5;
  }
  if (key=='g'||key=='G') {
    TT-=0.5;
  }
}
void mouseWheel(MouseEvent event) {
  float e = event.getCount();
  if (e == -1) r+=10;
  if (e == 1) r-=10;
}

核心思想即:修改參數(shù)方程装悲,對其插值昏鹃。每個粒子給定一個角度和角度增量,帶入方程诀诊,即可產(chǎn)生軌跡洞渤。又由于插值的關(guān)系,使得粒子運動不到軌道上属瓣,即會向下一個點插值载迄,從而改變方向,就像被軌跡牽著走一樣抡蛙。

其中的關(guān)鍵函數(shù)“Lerp”护昧,為插值函數(shù)。前三個參數(shù)為目標(biāo)點粗截,這里我們的目標(biāo)點就是方程確定的軌跡點(三維惋耙。將第三個值z軸設(shè)為0)。最后一個參數(shù)為百分比熊昌,表示當(dāng)前這個向量要均勻地向目標(biāo)點插值百分之多少绽榛。它似乎會自動每幀返回到當(dāng)前向量中。

對于參數(shù)方程:




經(jīng)過實驗總結(jié)婿屹,它的形狀:
1灭美、當(dāng)x和y的三角函數(shù)周期相同,相位相同(括號里東西一模一樣)時——圓
2昂利、當(dāng)x和y的周期不同時(theta進(jìn)行乘除)——不同的波形
3届腐、當(dāng)x和y的周期相同,相位不同(theta進(jìn)行加減)時——不同的橢圓
4页眯、當(dāng)x和y的三角函數(shù)名一樣(都為sin或cos)時——遵從第2梯捕、3條厢呵,但形狀不同(橢圓方向窝撵、扁圓之類的);若周期相同襟铭,相位相同碌奉,則為一條直線段
5、當(dāng)x和y的三角函數(shù)名顛倒時——x和y軸顛倒
6寒砖、當(dāng)三角函數(shù)中有負(fù)值時——與正值時的環(huán)繞順序相反(順時針變逆時針)

暫時就總結(jié)了這么多赐劣。

第4章針對習(xí)作:簡單的粒子系統(tǒng)

其實現(xiàn)過程基本糅合了前四章的內(nèi)容。沒有什么新東西哩都,只是用了粒子管理器這個類去管理粒子魁兼,而主程序只需要管理粒子管理器。

鼠標(biāo)單擊之后就會生成一個粒子管理器漠嵌,從鼠標(biāo)位置向四周發(fā)射200個粒子咐汞。


程序4.0.gif

粒子會緩慢消失盖呼。

使用鍵盤來施加各種效果:
T——彈簧,將每個粒子都與鼠標(biāo)用彈簧相連化撕。粒子會被吸引几晤。
G——施加重力。
A——施加空氣阻力植阴。
Y——施加引力蟹瘾。
W——開啟振蕩。它會像上一個程序一樣掠手,將位置直接向軌跡插值憾朴。
Q、E——加減軌跡的參數(shù)TT以改變形狀惨撇。
滾輪——改變軌跡的大小伊脓。只在W開啟時有效。

由于有初速度魁衙,僅僅插值軌跡是無法將粒子全部拉到軌跡上的报腔。


動圖太大傳不上來

所以需要使躁動的粒子安靜下來,施加空氣阻力剖淀。在空氣阻力的作用下纯蛾,粒子的初速度漸漸被抹平。而位置插值不改變速度纵隔,所以即使看到粒子在高速運動翻诉,其“速度”變量的大小也是接近0的。此時就會明顯地被拉到軌道上捌刮。


image.png

添加彈簧力后會壓縮波形碰煌,把距離較遠(yuǎn)的點強行拉回;而引力做不到绅作,引力對于遠(yuǎn)點的影響太小了:
彈簧力拉扁的波形

單彈簧力的效果芦圾。粒子快速、大范圍的繞鼠標(biāo)做橢圓運動俄认。



加上空氣阻力个少,則快速收斂到點:

加上重力,粒子被拉成長條眯杏,并宛如真的彈簧一樣抖動:

加上引力夜焦,則由于越近,引力越大岂贩,粒子被加速拋出茫经,再由彈簧拉回,不斷反復(fù),形成類似波動的運動效果卸伞,視覺上如同煙花:

加上重力褥紫,則在頭部有小“煙花”:

代碼文本較多,很多都和前面的相同瞪慧。

粒子類中髓考,使用了一個與求分力再求合力等效的算法:求分加速度再求合加速度。這樣的好處是弃酌,重力完全不需要考慮氨菇,只需要將加速度加上豎直向下的9.8即可。

粒子漸漸消失妓湘,是由透明度得到的效果查蓉。當(dāng)粒子本身的透明度為0時,其死亡榜贴。同時豌研,被粒子管理器從隊列移除。當(dāng)例子管理器中的粒子隊列里沒有任何粒子時唬党,它也會被主程序從管理器隊列中移除鹃共,以此保證長時間運行時的幀率。

粒子類與前面的mover類類似驶拱,不再給出文本霜浴。

下面給出粒子管理器類:

class LizManager {
  ArrayList<Liz> lizi;    //粒子隊列

  public LizManager() {
    lizi=new ArrayList<Liz>();
    float t=random(0, 100);    //泊林噪聲初值
    for (int i=0; i<num; i++) {
      lizi.add(new Liz(t));
      t+=0.007;
      lizi.get(i).display();
    }
  }

  void run() {        //運行
    for (int i=0; i<lizi.size(); i++) {
      Liz p=lizi.get(i);
      p.clearAc();        //每個粒子,清除加速度
      if (tanhuang) {
        p.tanhuangForce();  //計算彈簧力造成的分加速度并累加
      }
      if (isgravity) {
        p.useGravity();    //計算重力加速度并累加
      }
      if (isYinli) {
        p.useYinli();      //計算引力加速度并累加
      }
      if (wave) {
        p.useWave();      //進(jìn)行振蕩位置插值
      }
      p.update();        //更新位置蓝纲。關(guān)于空氣阻力的判斷放在了粒子類中
      p.Bounds();      //判斷是否越界
      p.display();      //顯示
      if (p.isDead()) {    //粒子是否死亡
        lizi.remove(i);  //死亡阴孟,粒子被移除
        i--;              //!K懊浴S浪俊!箭养!移除后下標(biāo)需要減一
      }
    }
  }
}

由于隊列移除時慕嚷,會自動填補空位,所以需要把索引后移一位露懒,這樣循環(huán)時自加闯冷,就不會漏掉隊列中任何一個元素砂心。

主程序的draw函數(shù):

void draw(){
  fill(0,5);
  rect(0,0,width,height);
  if(lizM.size()>0){
    for(int i=0;i<lizM.size();i++){
      lizM.get(i).run();
      if(lizM.get(i).lizi.size()<=0){    //這個粒子系統(tǒng)已經(jīng)沒有活粒子了懈词,移除
        lizM.remove(i);
        i--;
      }
    }
  }
}

非常簡潔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辩诞,一起剝皮案震驚了整個濱河市坎弯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖抠忘,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撩炊,死亡現(xiàn)場離奇詭異,居然都是意外死亡崎脉,警方通過查閱死者的電腦和手機拧咳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來囚灼,“玉大人骆膝,你說我怎么就攤上這事≡钐澹” “怎么了阅签?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蝎抽。 經(jīng)常有香客問我政钟,道長,這世上最難降的妖魔是什么樟结? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任养交,我火速辦了婚禮,結(jié)果婚禮上瓢宦,老公的妹妹穿的比我還像新娘层坠。我一直安慰自己,他們只是感情好刁笙,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布破花。 她就那樣靜靜地躺著,像睡著了一般疲吸。 火紅的嫁衣襯著肌膚如雪座每。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天摘悴,我揣著相機與錄音峭梳,去河邊找鬼。 笑死蹂喻,一個胖子當(dāng)著我的面吹牛葱椭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播口四,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼孵运,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蔓彩?” 一聲冷哼從身側(cè)響起治笨,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤驳概,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后旷赖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顺又,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年等孵,在試婚紗的時候發(fā)現(xiàn)自己被綠了稚照。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡俯萌,死狀恐怖锐锣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绳瘟,我是刑警寧澤雕憔,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站糖声,受9級特大地震影響斤彼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蘸泻,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一琉苇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悦施,春花似錦并扇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至昼汗,卻和暖如春肴熏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背顷窒。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工蛙吏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鞋吉。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓鸦做,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谓着。 傳聞我的和親對象是個殘疾皇子泼诱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348