前言
近日拜讀了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ì)變成這樣:
可以看到,這個(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)的一半和寬的一半怯邪。
效果如下:
通過(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)效果如下:
可以看到垒拢,已經(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à)出顏色不同的線了惶岭,效果如下:
最后寿弱,為了讓向量的顯示更加直觀,我使用text函數(shù)加入了文字標(biāo)識(shí)按灶,最終成品如下:
下面附上全部代碼:
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ū)中給出的定義如下:
繪制系統(tǒng)的實(shí)現(xiàn)中,還是先畫(huà)出一些向量铺罢,最后將它們的和點(diǎn)乘2艇挨,效果如下:
代碼如下:
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ì)算與繪制。