2023年1月11日 星期三

TC*(˙Ⱉ˙ฅ)​-week15

這周來寫如何讓母球撞擊牆壁反彈

先畫出一個視窗與兩顆球(紅色和藍色),兩球之間會有線相連,滑鼠鼠標與紅色球之間也有線相連(皆是指向圓心),並且紅色球可以透過滑鼠點擊而推進,大部分解釋都有註解在程式碼後

//想要做出撞球/彈珠台
//球互相撞
PVector ball,ballv1;//藍球的向量與速度
PVector ball2,ballv2;//紅球的向量與速度
void setup(){
  size(500,300);//size(1200,900);視窗大小
  ball = new PVector(100,150);//紅球的xy座標(100,150)
  ball2 = new PVector(400,150);//藍球的xy座標(400,150)
  ballv1 = new PVector(0,0);//紅球的速度初始化
  ballv2 = new PVector(0,0);//藍球的速度初始化
}
void draw(){
  background(#FFFFF2);//將背景清空才不會有殘影
  ball();//執行函式ball();
  ball2();//執行函式ball2();
  
  ball.x+=ballv1.x;//紅球X方向的速度慢慢增加
  ball.y+=ballv1.y;//紅球Y方向的速度慢慢增加
  ball2.x+=ballv2.x;//藍球X的速度慢慢增加
  ball2.y+=ballv2.y;//藍球Y方向的速度慢慢增加
  
  ballv1.x*=0.99;//紅球X方向的速度增加摩擦力
  ballv1.y*=0.99;//紅球Y方向的速度增加摩擦力
  ballv2.x *=0.99;//藍球X方向的速度增加摩擦力
  ballv2.y *=0.99;//藍球Y方向的速度增加摩擦力
  if(abs(ballv2.x)<0.001) ballv2.x=0;//如果藍球X方向速度(絕對值)小於0.001,則直接歸0
  if(abs(ballv2.y)<0.001) ballv2.y=0;//如果藍球Y方向速度(絕對值)小於0.001,則直接歸0
  if(abs(ballv1.x)<0.001) ballv1.x=0;//如果紅球X方向速度(絕對值)小於0.001,則直接歸0
  if(abs(ballv1.y)<0.001) ballv1.y=0;//如果紅球Y方向速度(絕對值)小於0.001,則直接歸0
  line(ball.x,ball.y,mouseX,mouseY);//畫出一條線,連接紅球與鼠標的(X,Y)座標
  line(ball.x,ball.y,ball2.x,ball2.y);//畫出一條線,連接紅球與藍球的(X,Y)座標
}
void ball(){//紅球
    fill(#FA9090);//粉紅色
    ellipse(ball.x, ball.y, 30,30);//大小30*30的球
    if(ball.x+ballv1.x>500-15 || ball.x+ballv1.x<15){
//如果X座標+X座標速度>500-15 且 X座標+X座標速度<15(總之就是不能超過X方向的視窗邊界
    ballv1.x = -ballv1.x;//就反彈
    }
    if(ball.y+ballv1.y>300-15 || ball.y+ballv1.y<15){
//如果Y座標+Y座標速度>300-15 且 Y座標+Y座標速度<15(總之就是不能超過Y方向的視窗邊界
    ballv1.y = -ballv1.y;//就反彈
  }
}
void ball2(){//藍球
    fill(#9CD0FC);//藍色的球
    ellipse(ball2.x, ball2.y, 30,30);//大小30*30的球
    if(ball2.x+ballv2.x>500-15 || ball2.x+ballv2.x<15){
//如果X座標+X座標速度>500-15 且 X座標+X座標速度<15(總之就是不能超過X方向的視窗邊界
      ballv2.x = - ballv2.x;//就反彈
    }
    if(ball2.y+ballv2.y>300-15 || ball2.y+ballv2.y<15){
//如果Y座標+Y座標速度>300-15 且 Y座標+Y座標速度<15(總之就是不能超過Y方向的視窗邊界
    ballv2.y = - ballv2.y;//就反彈
  }
}
void mousePressed(){//滑鼠點擊時
  ballv1.x = (ball.x-mouseX)/10.0;//X方向的速度會等於(紅球X座標-滑鼠X座標)/10
  ballv1.y = (ball.y-mouseY)/10.0;//Y方向的速度會等於(紅球Y座標-滑鼠Y座標)/10
}


目前可以看出藍色球是沒有任何反應的,所以我們接下來要寫碰撞偵測,當紅色的球碰到藍色的球,兩顆就會各自反彈移動

//想要做出撞球/彈珠台
//球互相撞
PVector ball,ballv1;//紅球的向量與速度
PVector ball2,ballv2;//藍球的像亮與速度
void setup(){
  size(500,300);//視窗大小
  ball = new PVector(100,150);//紅球的xy座標(100,150)
  ball2 = new PVector(400,150);//藍球的xy座標(400,150)
  ballv1 = new PVector(0,0);//紅球的速度初始化
  ballv2 = new PVector(0,0);//藍球的速度初始化
}
void draw(){
  background(#FFFFF2);//將背景清空才不會有殘影
  
  ball();//執行函式ball();
  ball2();//執行函式ball2();
  
  if( collision() ){//碰撞偵測
  }else{
  }    
    ball.x+=ballv1.x;//紅球X方向的速度慢慢增加
    ball.y+=ballv1.y;//紅球Y方向的速度慢慢增加
    ball2.x+=ballv2.x;//藍球X的速度慢慢增加
    ball2.y+=ballv2.y;//藍球Y方向的速度慢慢增加
  
    ballv1.x*=0.99;//紅球X方向的速度增加摩擦力
    ballv1.y*=0.99;//紅球Y方向的速度增加摩擦力
    ballv2.x *=0.99;//藍球X方向的速度增加摩擦力
    ballv2.y *=0.99;//藍球Y方向的速度增加摩擦力
    if(abs(ballv2.x)<0.001) ballv2.x=0;//如果藍球X方向速度(絕對值)小於0.001,則直接歸0
    if(abs(ballv2.y)<0.001) ballv2.y=0;//如果藍球Y方向速度(絕對值)小於0.001,則直接歸0
    if(abs(ballv1.x)<0.001) ballv1.x=0;//如果紅球X方向速度(絕對值)小於0.001,則直接歸0
    if(abs(ballv1.y)<0.001) ballv1.y=0;//如果紅球Y方向速度(絕對值)小於0.001,則直接歸0
    line(ball.x,ball.y,mouseX,mouseY);//畫出一條線,連接紅球與鼠標的(X,Y)座標
    line(ball.x,ball.y,ball2.x,ball2.y);//畫出一條線,連接紅球與藍球的(X,Y)座標
}
void ball(){//紅球
    fill(#FA9090);//粉色
    ellipse(ball.x, ball.y, 30,30);//大小30*30的球
    if(ball.x+ballv1.x>500-15 || ball.x+ballv1.x<15){
      //如果X座標+X座標速度>500-15 且 X座標+X座標速度<15(總之就是不能超過X方向的視窗邊界
    ballv1.x = -ballv1.x;//就反彈
    }
    if(ball.y+ballv1.y>300-15 || ball.y+ballv1.y<15){
      //如果Y座標+Y座標速度>300-15 且 Y座標+Y座標速度<15(總之就是不能超過Y方向的視窗邊界
    ballv1.y = -ballv1.y;//就反彈
  }
}
void ball2(){//藍球
    fill(#9CD0FC);//藍色的球
    ellipse(ball2.x, ball2.y, 30,30);//大小30*30的球
    if(ball2.x+ballv2.x>500-15 || ball2.x+ballv2.x<15){
      //如果X座標+X座標速度>500-15 且 X座標+X座標速度<15(總之就是不能超過X方向的視窗邊界
      ballv2.x = - ballv2.x;//就反彈
    }
    if(ball2.y+ballv2.y>300-15 || ball2.y+ballv2.y<15){
      //如果Y座標+Y座標速度>300-15 且 Y座標+Y座標速度<15(總之就是不能超過Y方向的視窗邊界
    ballv2.y = - ballv2.y;//就反彈
  }
}
void mousePressed(){//滑鼠點擊時
  ballv1.x = (ball.x-mouseX)/10.0;//X方向的速度會等於(紅球X座標-滑鼠X座標)/10
  ballv1.y = (ball.y-mouseY)/10.0;//Y方向的速度會等於(紅球Y座標-滑鼠Y座標)/10
}
boolean collision(){//碰撞
  if( dist(ball.x+ballv1.x , ball.y+ballv1.y , ball2.x+ballv2.x , ball2.y+ballv2.y)<30 ){  
    //計算兩點的距離,如果X和Y方向都小於30
    drawTangent();//執行切線程式
    drawInput();//執行輸出
    drawOutput();//執行輸入
    //ballv1.x=0; ballv1.y=0; ballv2.x=0; ballv2.y=0;
    return true;//有撞到
  }else return false;//沒有撞到
}
void drawTangent(){//計算切線
  float cx = (ball.x+ball2.x)/2;
  float cy = (ball.y+ball2.y)/2;
  ellipse(cx,cy,3,3);
  float dx = (ball.x-ball2.x);
  float dy = (ball.y-ball2.y);
  line(cx-dy*10, cy+dx*10, cx+dy*10, cy-dx*10);
  line(cx, cy, cx-ballv1.x*10, cy-ballv1.y*10);
  {
    float ddx=ballv1.x, ddy=ballv1.y;
    nx=ball.x-ball2.x;
    ny=ball.y-ball2.y;
    float len = sqrt(nx*nx+ny*ny);//計算向量len,sqrt是開根號
    nx /= len; 
    ny /= len;
    float inner = nx*ddx+ny*ddy;//內積
    nextX=ddx-inner*nx*2;
    nextY=ddy-inner*ny*2;
    solveMV();
    
    ballv2.x = nx*scale2;
    ballv2.y = ny*scale2;
    
    ballv1.x=nextX*scale;
    ballv1.y=nextY*scale;
    line(cx, cy, cx+nextX, cy+nextY);
  }
}
float nx, ny;
float scale, scale2;
float nextX, nextY;
void solveMV(){
  float m1=1, m2=1;
  float vx1=ballv1.x, vy1=ballv1.y;
  float vx2=ballv2.x, vy2=ballv2.y;
  // m1*vx1+m2*vx2 == m1*nextX*scale + m2*nx*scale2  (*ny)
  // m1*vy1+m2*vy2 == m1*nextY*scale + m2*ny*scale2  (*nx)
  // ny*(m1*vx1+m2*vx2)-nx*(m1*vy1+m2*vy2) == (ny*m1*nextX-nx*m1*nextY)  *scale
  scale = (ny*(m1*vx1+m2*vx2)-nx*(m1*vy1+m2*vy2) ) / (ny*m1*nextX-nx*m1*nextY);
  scale2 = (m1*vx1+m2*vx2-m1*nextX*scale)/(m2*nx);
  println(scale, scale2);//為了確認計算結果有無錯誤,要輸出看看
  println("eq1", m1*vx1+m2*vx2, m1*nextX*scale + m2*nx*scale2);//為了確認計算結果有無錯誤,要輸出看看
  println("eq2", m1*vy1+m2*vy2, m1*nextY*scale + m2*ny*scale2);//為了確認計算結果有無錯誤,要輸出看看
}
void drawInput(){
  line(ball.x,ball.y, ball.x+ballv1.x*10, ball.y+ballv1.y*10);//畫出切線
}
void drawOutput(){
  float dx=ballv1.x*10, dy=ballv1.y*10;
  float nx=ball.x-ball2.x, ny=ball.y-ball2.y;
  float len = sqrt(nx*nx+ny*ny);//計算向量
  nx /= len; 
  ny /= len;
  float inner = nx*dx+ny*dy;
  nextX=dx-inner*nx*2;
  nextY=dy-inner*ny*2;
  line(ball.x,ball.y, ball.x+nextX, ball.y+nextY);
}


這部分的程式是運用入射角等於反射角的概念去寫,但是入射角並不等於反射角!!
因為兩顆球都會動,所以並不適用於這個定律
而是要利用動量守恆來讓兩顆球去動作
上面雖然兩顆球可以彈來彈去,不過移動軌跡就會是錯誤的


沒有留言:

張貼留言