《代碼本色:用編程模擬自然系統(tǒng)》習(xí)作——第1章:向量

前言

近日拜讀了Daniel Shiffman先生的著作——《代碼本色:用編程模擬自然系統(tǒng)》海铆,決定做一組習(xí)作,來(lái)對(duì)書(shū)中提到的隨機(jī)行為及牛頓運(yùn)動(dòng)學(xué)進(jìn)行理解洋丐,并對(duì)一些實(shí)例進(jìn)行拓展學(xué)習(xí)滚停,從而提升自己相關(guān)方面的知識(shí)水平和實(shí)踐能力霉囚。

《代碼本色》第1章概述

在第1章中谭溉,作者向我們介紹了"向量"這一概念墙懂,在Processing中,向量的使用非常關(guān)鍵扮念。本章包含了向量的加法损搬、減法、乘法柜与、歸一化等基本知識(shí)巧勤。作者用BallBounce這個(gè)例子來(lái)講述PVector的一些用法,使人耳目一新弄匕。

習(xí)作和創(chuàng)作過(guò)程

由于之前自學(xué)過(guò)相關(guān)知識(shí)颅悉,并已經(jīng)在之前的一次作業(yè)中使用過(guò)PVector,實(shí)現(xiàn)了鼠標(biāo)跟隨的功能粘茄,在本文中就對(duì)當(dāng)時(shí)的創(chuàng)作進(jìn)行簡(jiǎn)短的回顧。
隨后秕脓,我又根據(jù)書(shū)中向量的一些加減法柒瓣,制作了一個(gè)交互式的向量加減法可視化軟件。

一群點(diǎn)跟隨鼠標(biāo)運(yùn)動(dòng)

那么首先吠架,來(lái)回顧一下我之前做的作品吧:


前作

這個(gè)作品運(yùn)用的物理知識(shí)在書(shū)中的第1.7節(jié)和1.8節(jié)中進(jìn)行了介紹芙贫。該作品的主要代碼如下:

class Point {
  PVector Position, speed, accelerate; 
  Point(){
  Position = new PVector(random(width), random(height));
  speed= new PVector();
  accelerate= new PVector();
  }
  void render(int R,int G,int B){
  fill(R,G,B);
  noStroke();
  ellipse(Position.x,Position.y,10,10);
  }
  void update(){
  accelerate = new PVector(mouseX-Position.x, mouseY -Position.y); 
  accelerate.limit(1);
  speed.add(accelerate);
  speed.limit(20);
  Position.add(speed);
  }
}
Point [] points = new Point[50];
void setup(){
  size(800,800);
  background(255);
  for( int i =0; i < points.length; i++){
    points[i] = new Point();
  }
}
void draw(){
  int a=millis()/10;
  int b=millis()/50;
  int c=millis()/100;
  fill(255,255,255,50);
  rect(0,0,800,800);
  for( int i =0; i < points.length; i++){
    points[i].update(); 
    points[i].render(c,b,a); 
  } 
}

可以看到,我建立了一個(gè)Point類傍药,其中建立了Position, speed, accelerate三個(gè)PVector向量磺平,并使用Limit函數(shù)限制他們的大小。然后根據(jù)鼠標(biāo)位置和點(diǎn)的位置的差值計(jì)算加速度拐辽,然后將加速度累加給速度拣挪,速度累加給位置。就是這樣的一個(gè)經(jīng)典的跟隨鼠標(biāo)的算法俱诸。同時(shí)菠劝,使用millis()函數(shù)獲取了時(shí)間的變化,實(shí)現(xiàn)了顏色的平滑變化睁搭。

交互式向量計(jì)算與繪制——加法

首先赶诊,我參考1.41中向量的減法的示例編寫(xiě)了一個(gè)向量繪制的功能笼平。
第一步,要畫(huà)一條從屏幕中間到鼠標(biāo)點(diǎn)的線——這聽(tīng)起來(lái)很簡(jiǎn)單舔痪,但是卻有不少細(xì)節(jié)寓调。我是以1.4.1向量的減法為例進(jìn)行代碼的編寫(xiě)的,因?yàn)檫@條線也可以理解成是鼠標(biāo)位置的坐標(biāo)向量與屏幕中心的坐標(biāo)向量相減得出的結(jié)果锄码。
代碼如下:

  background(0);
  PVector mouse = new PVector(mouseX, mouseY);
  PVector center = new PVector(width/2, height/2);
  mouse.sub(center);
  translate(width/2, height/2);
  strokeWeight(2);
  stroke(255);
  line(0, 0, mouse.x, mouse.y);//畫(huà)新的線

這里需要注意的是這一行

  translate(width/2, height/2);

這個(gè)代表什么呢夺英?在之前的自畫(huà)像文章中我提到過(guò),translate函數(shù)表示在這一幀將坐標(biāo)系平移一定的量巍耗。這里為什么要平移半個(gè)屏幕的寬和半個(gè)屏幕的高呢秋麸?這是因?yàn)閙ouseX和mouseY這兩個(gè)變量都是根據(jù)屏幕左上角為原點(diǎn)的,而我們想要的是它以屏幕中心為原點(diǎn)炬太,如果不加這一句灸蟆,結(jié)果就會(huì)變成這樣:


錯(cuò)誤的示例.gif

可以看到,這個(gè)程序的確將鼠標(biāo)位置和屏幕中心的差值計(jì)算出來(lái)了亲族,但是卻從原點(diǎn)開(kāi)始畫(huà)了炒考。加上translate語(yǔ)句后,就解決了這個(gè)問(wèn)題霎迫。

第二步斋枢,為了將每次畫(huà)的點(diǎn)存下來(lái),我使用了上一篇文章中介紹過(guò)了ArrayList數(shù)組:

ArrayList<PVector>Points=new ArrayList<PVector>();

然后知给,編寫(xiě)了mouseClicked()函數(shù)瓤帚,也就是說(shuō),每次點(diǎn)擊鼠標(biāo)涩赢,就可以添加一個(gè)新的點(diǎn)加入ArrayList:


void mouseClicked()
{
   PVector newPoint =new PVector();
  newPoint.x=mouseX-width/2;
  newPoint.y=mouseY-height/2;
  Points.add(newPoint);
  }

注意戈次!這里的點(diǎn)也需要進(jìn)行轉(zhuǎn)換!筒扒!分別減去屏幕長(zhǎng)的一半和寬的一半怯邪。
效果如下:

GIF2.gif

通過(guò)點(diǎn)擊鼠標(biāo),就可以添加新的線了花墩,非常方便悬秉。
那么下一步,我們就要進(jìn)入正題了——計(jì)算這些向量的和向量冰蘑。為此和泌,我添加了一個(gè)新的交互方式——鼠標(biāo)右鍵點(diǎn)擊。
但由于之前編寫(xiě)的mouseClicked()函數(shù)是不區(qū)分鼠標(biāo)左右鍵的祠肥,為此允跑,需要對(duì)mouseClicked函數(shù)的內(nèi)容進(jìn)行改寫(xiě)。
在Processing中,通常我們用mouseButton變量來(lái)判斷我們按下的鍵位聋丝。LEFT為左鍵索烹,RIGHT為右鍵,所以弱睦,我們將之前寫(xiě)的改成這樣:

void mouseClicked()
{
  if(mouseButton == LEFT)
  {
   PVector newPoint =new PVector();
  newPoint.x=mouseX-width/2;
  newPoint.y=mouseY-height/2;
  Points.add(newPoint);
  }
}

然后百姓,加入鼠標(biāo)右鍵的相應(yīng)內(nèi)容:


void mouseClicked()
{
  if(mouseButton == LEFT)
  {
   PVector newPoint =new PVector();
  newPoint.x=mouseX-width/2;
  newPoint.y=mouseY-height/2;
  Points.add(newPoint);
  }
  if(mouseButton == RIGHT)
  {
    CalcTotalVector();
  }
}

可以看到,我將計(jì)算和向量的操作放到了 CalcTotalVector()函數(shù)中進(jìn)行處理况木。 CalcTotalVector()函數(shù)的代碼如下:

void CalcTotalVector()
{
PVector sum=new PVector();
  for(int i=0;i<Points.size();i++)
  {
    sum.x+=Points.get(i).x;
    sum.y+=Points.get(i).y;
  }
}
Points.add(sum);

實(shí)現(xiàn)效果如下:

GIF3.gif

可以看到垒拢,已經(jīng)實(shí)現(xiàn)了正確的結(jié)果,上圖中第四條最短的線就是前三個(gè)向量的和向量火惊。但是由于我是直接將新的向量存入Points列表中求类,隨意最后出來(lái)的線很難和之前的區(qū)分,于是我進(jìn)行了改進(jìn)屹耐。
首先尸疆,將新生成的點(diǎn)單獨(dú)存取至一個(gè)全局變量中:

PVector sum=new PVector();

隨后,在draw函數(shù)里添加下面這段代碼:

  //畫(huà)和向量
  stroke(255,255,0);
  line(0,0,sum.x,sum.y);

這樣就可以畫(huà)出顏色不同的線了惶岭,效果如下:


GIF4.gif

最后寿弱,為了讓向量的顯示更加直觀,我使用text函數(shù)加入了文字標(biāo)識(shí)按灶,最終成品如下:

GIF5.gif

下面附上全部代碼:

ArrayList<PVector>Points=new ArrayList<PVector>();
PVector sum=new PVector();
void setup(){
  size(800,800);
}

void draw()
{
  background(0);
  PVector mouse = new PVector(mouseX, mouseY);
  PVector center = new PVector(width/2, height/2);
  mouse.sub(center);
  translate(width/2, height/2);
  strokeWeight(2);
  stroke(255);
  line(0, 0, mouse.x, mouse.y);//畫(huà)新的線
  
  //畫(huà)之前確認(rèn)的線
  for(int i=0;i<Points.size();i++)
  {
    line(0,0,Points.get(i).x,Points.get(i).y);
  }
  //畫(huà)和向量
  stroke(255,255,0);
  line(0,0,sum.x,sum.y);
  //文字標(biāo)識(shí):
  translate(-width/2, -height/2);
  textSize(32);
  for(int i=0;i<Points.size();i++)
  {
  text("Vector"+i+":("+Points.get(i).x+","+Points.get(i).y+")",5,32+i*32);
  }
  text("SumVector:("+sum.x+","+sum.y+")",5,height-32);
}

void mouseClicked()
{
  if(mouseButton == LEFT)
  {
   PVector newPoint =new PVector();
  newPoint.x=mouseX-width/2;
  newPoint.y=mouseY-height/2;
  Points.add(newPoint);
  }
  if(mouseButton == RIGHT)
  {
    CalcTotalVector();
  }
}

void CalcTotalVector()
{
  for(int i=0;i<Points.size();i++)
  {
    sum.x+=Points.get(i).x;
    sum.y+=Points.get(i).y;
  }
}

交互式向量計(jì)算與繪制——減法

向量的減法原理在書(shū)中的定義如下:


向量減法

實(shí)現(xiàn)的過(guò)程和加法比較類似症革,這里就不詳細(xì)的講述了,最終實(shí)現(xiàn)的效果如下:


減法

下面附上代碼:
ArrayList<PVector>Points=new ArrayList<PVector>();
PVector sub=new PVector();
void setup(){
  size(800,800);
}

void draw()
{
  background(0);
  PVector mouse = new PVector(mouseX, mouseY);
  PVector center = new PVector(width/2, height/2);
  mouse.sub(center);
  translate(width/2, height/2);
  strokeWeight(2);
  stroke(255);
  line(0, 0, mouse.x, mouse.y);//畫(huà)新的線
  
  //畫(huà)之前確認(rèn)的線
  for(int i=0;i<Points.size();i++)
  {
    line(0,0,Points.get(i).x,Points.get(i).y);
  }
  //畫(huà)差向量
  stroke(255,0,0);
  line(0,0,sub.x,sub.y);
  //文字標(biāo)識(shí):
  translate(-width/2, -height/2);
  textSize(32);
  for(int i=0;i<Points.size();i++)
  {
  text("Vector"+i+":("+Points.get(i).x+","+Points.get(i).y+")",5,32+i*32);
  }
  text("SubVector:("+sub.x+","+sub.y+")",5,height-32);
}

void mouseClicked()
{
  if(mouseButton == LEFT)
  {
   PVector newPoint =new PVector();
  newPoint.x=mouseX-width/2;
  newPoint.y=mouseY-height/2;
  Points.add(newPoint);
  }
  if(mouseButton == RIGHT)
  {
    CalcTotalVector();
  }
}

void CalcTotalVector()
{
  sub.x=Points.get(0).x;
  sub.y=Points.get(0).y;
  for(int i=1;i<Points.size();i++)
  {
    sub.x-=Points.get(i).x;
    sub.y-=Points.get(i).y;
  }
}

交互式向量計(jì)算與繪制——點(diǎn)乘

點(diǎn)乘的定義也比較好理解鸯旁,相當(dāng)于是每個(gè)元素都乘以一個(gè)常數(shù)噪矛,書(shū)中給出的定義如下:


點(diǎn)乘的定義

繪制系統(tǒng)的實(shí)現(xiàn)中,還是先畫(huà)出一些向量铺罢,最后將它們的和點(diǎn)乘2艇挨,效果如下:


點(diǎn)乘

代碼如下:

ArrayList<PVector>Points=new ArrayList<PVector>();
PVector mul=new PVector();
PVector sum=new PVector();
void setup(){
  size(800,800);
}

void draw()
{
  background(0);
  PVector mouse = new PVector(mouseX, mouseY);
  PVector center = new PVector(width/2, height/2);
  mouse.sub(center);
  translate(width/2, height/2);
  strokeWeight(2);
  stroke(255);
  line(0, 0, mouse.x, mouse.y);//畫(huà)新的線
  
  //畫(huà)之前確認(rèn)的線
  for(int i=0;i<Points.size();i++)
  {
    line(0,0,Points.get(i).x,Points.get(i).y);
  }
  //畫(huà)和向量
  stroke(0,255,0);
  line(0,0,mul.x,mul.y);
  //文字標(biāo)識(shí):
  translate(-width/2, -height/2);
  textSize(32);
  for(int i=0;i<Points.size();i++)
  {
  text("Vector"+i+":("+Points.get(i).x+","+Points.get(i).y+")",5,32+i*32);
  }
  text("MulVector:("+mul.x+","+mul.y+")",5,height-32);
}

void mouseClicked()
{
  if(mouseButton == LEFT)
  {
   PVector newPoint =new PVector();
  newPoint.x=mouseX-width/2;
  newPoint.y=mouseY-height/2;
  Points.add(newPoint);
  }
  if(mouseButton == RIGHT)
  {
    CalcTotalVector();
  }
}

void CalcTotalVector()
{
  for(int i=0;i<Points.size();i++)
  {
    sum.x+=Points.get(i).x;
    sum.y+=Points.get(i).y;
  }
  mul.x=2*sum.x;
  mul.y=2*sum.y;
}

總結(jié)

本文介紹了《代碼本色:用編程模擬自然系統(tǒng)》第1章的主要內(nèi)容,并在示例的基礎(chǔ)上進(jìn)行了拓展性的創(chuàng)作畏铆,創(chuàng)作了:鼠標(biāo)跟隨點(diǎn)的生成雷袋、交互式向量加法計(jì)算與繪制吉殃、交互式向量減法計(jì)算與繪制辞居、交互式向量乘法計(jì)算與繪制。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛋勺,一起剝皮案震驚了整個(gè)濱河市瓦灶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抱完,老刑警劉巖贼陶,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡碉怔,警方通過(guò)查閱死者的電腦和手機(jī)烘贴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撮胧,“玉大人桨踪,你說(shuō)我怎么就攤上這事∏凵叮” “怎么了锻离?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)墓怀。 經(jīng)常有香客問(wèn)我汽纠,道長(zhǎng),這世上最難降的妖魔是什么傀履? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任虱朵,我火速辦了婚禮,結(jié)果婚禮上啤呼,老公的妹妹穿的比我還像新娘卧秘。我一直安慰自己,他們只是感情好官扣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布翅敌。 她就那樣靜靜地躺著,像睡著了一般惕蹄。 火紅的嫁衣襯著肌膚如雪蚯涮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天卖陵,我揣著相機(jī)與錄音遭顶,去河邊找鬼。 笑死泪蔫,一個(gè)胖子當(dāng)著我的面吹牛棒旗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播撩荣,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼铣揉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了餐曹?” 一聲冷哼從身側(cè)響起逛拱,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎台猴,沒(méi)想到半個(gè)月后朽合,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體俱两,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年曹步,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宪彩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讲婚,死狀恐怖毯焕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情磺樱,我是刑警寧澤纳猫,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站竹捉,受9級(jí)特大地震影響芜辕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜块差,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一侵续、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧憨闰,春花似錦状蜗、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至泽示,卻和暖如春缸血,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背械筛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工捎泻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人埋哟。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓笆豁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親赤赊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闯狱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344