第一部分:0~2章的針對習(xí)作
0~2章的主要內(nèi)容分別為隨機數(shù)生成、向量的使用和牛頓運動力學(xué)的簡單模擬冻璃。
第0章針對習(xí)作:隨機點線生成
其可以當(dāng)作是燈光评肆,或者是雨點,或者是你認(rèn)為的任何東西陕赃。表現(xiàn)圖如下:
在屏幕上不斷出現(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)如下:
分別是求和驳庭、求差、數(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)力宁赤,開啟空氣阻力和重力。
操作如下:
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ī)則形成各種波形,并“流動”宠页。視覺效果很好左胞。
一些操作方法及功能:
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坡脐,單擊后可見其完整波形如圖:
代碼中使用lerp函數(shù),向這個軌跡插值房揭,每次插值0.5%备闲,由于角速度變化的不同,形成了上面那張圖的效果捅暴。單擊后即每次插值100%恬砂,直接移動到這個軌跡上,形成確定的圖案蓬痒。
按下S泻骤,改變方程中TT的運算規(guī)則,變?yōu)?div id="7jrgkiy" class="image-package">