/* built with Studio Sketchpad:
* https://sketchpad.cc
*
* observe the evolution of this sketch:
* https://studio.sketchpad.cc/sp/pad/view/ro.LCAnwZGRd25/rev.160
*
* authors:
* H.C. Chen
* license (unless otherwise specified):
* creative commons attribution-share alike 3.0 license.
* https://creativecommons.org/licenses/by-sa/3.0/
*/
// 這個盒子裡的球有很大的摩擦力,所以小心一點放,你可以把它們堆高起來。找人來比賽,一人一次,看誰先把球堆到天花板? 秘訣提示:該你時,如果覺得基礎不穩,不妨先輕輕用一個球來把它們擠穩一點。這樣做事很像高手喔!?
// 你也可以一起來改下面的程式,讓它更好玩。 歡迎來信 [email protected] 提供你的意見。 hcchen5600 2012/07/08 12:08:47
// 改良 'Balls' from http://studio.sketchpad.cc/sp/pad/view/ro.9yKXNlKdKxFqH/rev.126
frameRate = 60;
int numBalls = 15; //int declares numBalls as 15 (assigning it a value type)
float spring = 0.7; //float is a continuous value,
float gravity = 0.9;
float wallBounce = 0.9; // original -0.9 , 四周圍牆的材質。超過一變成超強力彈性牆。零就是超級海綿完全吸收任何撞擊。
float friction = 0.4; // 我給它加上「摩擦係數」 hcchen5600 2012/07/01 10:38:49
float maxvx = 50; // 最高速度如果不加限制,惡搞之下有時候會整個失控變成無限高速亂飛一團。
float maxvy = 50;
Ball[] balls = new Ball[numBalls]; //[] gives you access to the array - variable name[] access
void setup()
{
size(400, 400);
noStroke(); //means no outline
// stroke(#0000FF); // black ball ID text is not controled by stroke()
// smooth(); //all drawn with smooth edges
// create all the balls
for (int i = 0; i < numBalls; i++) {
balls[i] = new Ball(random(width), random(height), random(75, 40), i, balls);
}
}
// --------------- 單步執行 debug 用的 ----------------------
int onestep = 0;
void keyReleased()
{
onestep = 0;
}
//-----------------------------------------------------------
void draw()
{
// if (!keyPressed || onestep) return; // 單步執行 debug 用的
background(35, 89, 35);
for (int i = 0; i < numBalls; i++) {
balls[i].collide();
balls[i].move();
balls[i].display();
}
// onestep = 1; // 單步執行 debug 用的
}
class Ball {
// 這些是 Ball() 的 properties
float x, y; // position
float diameter; // 直徑
float radius; // 半徑
float vx = 0; // throws to the right
float vy = 0; // 地心引力方向. 一開始的動量 (vx,vy) 是靜止的。每 frame vy 皆加上 gravity 此呈現為加速度是即地心引力,這麼簡單!
int id;
Ball[] others; // 咦! each Ball has its own image of (all) others. Simply to have the symbol of the array.
boolean mousepressed = false;
Ball(float xin, float yin, float din, int idin, Ball[] oin) {
x = xin;
y = yin;
diameter = din;
radius = diameter / 2;
id = idin;
others = oin;
}
void collide() {
for (int i = id + 1; i < numBalls; i++) { // 不管以前的 ball 只管自己以後兩兩之間的 collision
// the distance from this ball to next ball
float dx = others[i].x - x;
float dy = others[i].y - y;
float distance = sqrt(dx*dx + dy*dy);
float minDist = others[i].radius + radius; // 緊貼兩 ball 的球心距離。
//println("frameCount is " + frameCount);
//for (int j=0; j<numBalls; j++){
// println("Ball" +j+ " diameter=" +others[j].diameter+ " x=" +others[j].x+ " y=" +others[j].y );
//}
//println("id=" +id+ " i=" +i);
//println("distance=" +distance+ " minDist="+minDist);
if (distance < minDist) { // 撞上了! 當兩球相撞時,總動量不變。
float angle = atan2(dy, dx); // 以本 ball 朝向 next ball 的方向。物理上,到底誰撞誰?應該是對稱平等的。
// 我覺得不必如此費事算角度、算投影。我覺得 targetX,targetY 不就是 dx,dy 嗎? 一試結果不對。
// 首先,(dx,dy) 已經小於 minDist 了,不是個能用的超現實數據。 但是角度應該一致吧?也不對。加上 (x,y) 以後就不然了。
// 我想 (targetX,targetY) 既然是 (x,y) 加上 (cos(angle)*minDist, sin(angle)*minDist) 那豈不就是跟它相撞的球「當在的位置」了嗎? 對了!
// (targetX,targetY) 是跟本球相撞的球當在的位置。 目前已經撞進球體裡面來了。
float targetX = x + cos(angle) * minDist; // 球心連線在 x 軸上的投影加上 x 即為另一球的 x軸 位置。
float targetY = y + sin(angle) * minDist;
float ax = (targetX - others[i].x) * spring; // 另一球當在的位置與目前互相撞進球體之內的差距 乘上 彈性係數。方向是另一球該修正的方向。
float ay = (targetY - others[i].y) * spring;
vx -= ax; // 本球該修正的方向與另一球相反。
vy -= ay;
//println("angle=" +angle);
//println("targetX=" + targetX);
//println("targetY=" + targetY);
//println("dx=" +dx);
//println("dy=" +dy);
//println("ax=" +ax);
//println("ay=" +ay);
//println("vx=" +vx);
//println("vy=" +vy);
others[i].vx += ax;
others[i].vy += ay;
}
}
}
// move() 移動球體時,一併考慮計算牆面、地板的反彈。
// 把「力」看成 (vx,vy) 即相對於目前位置要改變多少的瞬時位移,分別在兩個座標軸上算出來。「瞬時」使得「速度」與「加速度」都反應為「位移」。
// 本來還以為要算反射角、入射角的,根本不必! 適時把 vx 或 vy 倒向就好了。
void move() {
if (mousepressed) return;
vy += gravity; // 由此看出,「力」表現為位移的幅度,而重力就是在 vy 上加成. vx,vy 是該 ball 的瞬時向量。
vx += vx>0? -friction : friction ;
vy += vy>0? -friction : friction ;
vx = abs(vx)<friction? 0 : vx ;
vy = abs(vy)<friction? 0 : vy ;
vx = abs(vx)>maxvx? maxvx*vx/abs(vx) : vx ;
vy = abs(vy)>maxvy? maxvy*vy/abs(vy) : vy ;
x += vx;
y += vy;
// 如果不考慮牆面,以上就是 move() 了!
if (x + radius > width) { // 超過 canvas 右邊
x = width - radius; // 無法超過牆面,位置就在牆面上。
vx *= -wallBounce; // 牆壁的反彈力 [ ] 為何這裡用加的,而下面卻用乘的? 用加的可能是 typo! 改正之。
} else if (x - radius < 0) { // 超過 canvas 左邊
x = radius;
vx *= -wallBounce;
}
if (y + radius > height) { // 撞上 canvas 地板
y = height - radius;
vy *= -wallBounce;
} else if (y - radius < 0) { // 超過 canvas 上邊
y = radius;
vy *= -wallBounce;
}
}
void display() {
fill(255, 204); // specify ball color
ellipse(x, y, diameter, diameter);
fill(0); // specify font color text color
text(id, x, y);
}
boolean mouseOver(int mx, int my) {
return sqrt((x-mx)*(x-mx) + (y-my)*(y-my)) <= radius; // 勾股弦定理 check if the distance from (x,y) to (mx,my) is less than radius.
}
boolean mousePressed() {
if (mouseOver(mouseX,mouseY)){
mousepressed = true;
}
}
boolean mouseReleased() {
mousepressed = false;
}
boolean mouseDragged() {
if (mousepressed){
x = mouseX;
y = mouseY;
vx = 0;
vy = 0;
}
}
}
// ------------- Balls drag and drop ---------------------------------------
void mousePressed() { // This is a callback function
for(int i=0, end=balls.length; i<end; i++) { // 一個個 ball 檢查自己對 mouse pressed 的工作。
balls[i].mousePressed();
}
}
void mouseReleased() { // This is a callback function
for(int i=0, end=balls.length; i<end; i++) { // 看到 mouse release 就全 release
balls[i].mouseReleased();
}
}
void mouseDragged() { // This is a callback function
for(int i=0, end=balls.length; i<end; i++) { // mouseDragged() 時也是各自做自己的
balls[i].mouseDragged();
}
}