引言
在學(xué)習(xí)JavaScript
的時(shí)候荠藤,看到一個(gè)練習(xí)事例才睹,就想到能不能做成類(lèi)似Windows
屏幕保護(hù)氣泡那種效果潘酗,經(jīng)過(guò)不斷思考嘗試模软,最后做出的效果如下圖:
思路
其實(shí)上面的那種效果就是模擬理想物理環(huán)境下的多個(gè)小球非對(duì)心碰撞(對(duì)心碰撞是其特殊情況),所謂理想物理情況就是沒(méi)有外力作用的封閉系統(tǒng)厨钻,內(nèi)部遵循動(dòng)量守恒定律和能量守恒定律扼雏。假設(shè)小球A和小球B的質(zhì)量分別為????
和????
,初始速度分別為????
和????
夯膀,碰撞后的速度分別為????'
和????'
诗充,兩個(gè)小球的碰撞瞬間的狀態(tài)如下圖:
其中??????
和??????
是兩小球沿球心連線(xiàn)方向上的分速度,??????
和??????
是兩小球垂直球心連線(xiàn)方向上的分速度诱建。碰撞后蝴蜓,由于兩小球在垂直球心連線(xiàn)方向上沒(méi)有力的相互作用,所以速度不變,還是??????
和??????
茎匠,沿球心連線(xiàn)方向上的分速度為??????'
和??????'
格仲。運(yùn)用以下物理公式:
能量守恒定律:
?????????2/2+?????????2/2=?????????'2/2+?????????'2/2
向量運(yùn)算:
????2= ??????2+??????2
????2= ??????2+??????2
????'2= ??????'2+??????2
????'2= ??????'2+??????2
推導(dǎo)得出:
???????????2/2+???????????2/2=???????????'2/2+???????????'2/2
再聯(lián)合動(dòng)量守恒定律:
???????????+???????????=???????????'+???????????'
推導(dǎo)得出:
??????'=(???????(????-????)+2????????????)/(????+????)
??????'=(???????(????-????)+2????????????)/(????+????)
最后合成碰撞后的速度????'
和????'
就ok了!
代碼實(shí)現(xiàn)
// 小球?qū)ο髽?gòu)造函數(shù)
function Ball(x, y, speedX, speedY, color, radius, density) {
this.x = x;
this.y = y;
this.speedX = speedX;
this.speedY = speedY;
this.color = color;
this.radius = radius;
this.density = density;
}
// 繪制
Ball.prototype.draw = function () {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
}
// 邊界碰撞檢測(cè)
Ball.prototype.borderCollisionDetect = function () {
if ((this.x + this.radius) >= width && this.speedX > 0) {
this.speedX *= -1;
}
if ((this.x - this.radius) <= 0 && this.speedX < 0) {
this.speedX *= -1;
}
if ((this.y + this.radius) >= height && this.speedY > 0) {
this.speedY *= -1;
}
if ((this.y - this.radius) <= 0 && this.speedY < 0) {
this.speedY *= -1;
}
}
// 創(chuàng)建小球?qū)ο?function createBalls() {
require(['utils'], function (utils) {
while (balls.length < 8) {
var ball = new Ball(
utils.random(0, width), // x
utils.random(0, height), // y
utils.random(1, 8), // speedX
utils.random(1, 8), // speedY
'rgb('+utils.random(0, 255) +','+ utils.random(0, 255)+','+ utils.random(0, 255) +')',
30, // radius
1 // density
);
balls.push(ball);
}
});
}
// 更新小球速度和位置
function update() {
for (let i = 0; i < balls.length; i++) {
balls[i].borderCollisionDetect();
for (let j = i + 1; j < balls.length; j++) {
if (ballsCollisionDetect(balls[i], balls[j])) {
collide(balls[i], balls[j]);
}
}
// 更新位置
balls[i].x += balls[i].speedX;
balls[i].y += balls[i].speedY;
}
}
// 碰撞檢測(cè)
function ballsCollisionDetect(ball1, ball2) {
// 當(dāng)前距離
var dx = ball1.x - ball2.x;
var dy = ball1.y - ball2.y;
var distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
// 預(yù)測(cè)下一時(shí)刻會(huì)不會(huì)碰撞
let dx_next = ball1.x + ball1.speedX - ball2.x - ball2.speedX;
let dy_next = ball1.y + ball1.speedY - ball2.y - ball2.speedY;
let distance_next = Math.sqrt(Math.pow(dx_next, 2) + Math.pow(dy_next, 2));
if (distance_next < ball1.radius + ball2.radius && distance_next < distance) {
return true;
}
return false;
}
// 更新碰撞后的狀態(tài)
function collide(ball1, ball2) {
require(['Vector2d'], function (Vector2d) {
// 初始速度向量
let speed_ball1_initial = new Vector2d(ball1.speedX, ball1.speedY);
let speed_ball2_initial = new Vector2d(ball2.speedX, ball2.speedY);
// 球心方向單位向量
let s = new Vector2d(ball2.x - ball1.x, ball2.y - ball1.y);
s = s.normalize();
// 垂直球心方向單位向量
let t = s.rotate(Math.PI / 2);
// 速度在球心向量上的分速度投影
let speed_ball1_initial_sc = speed_ball1_initial.dotProduct(s)/s.length();
let speed_ball2_initial_sc = speed_ball2_initial.dotProduct(s)/s.length();
// 速度在垂直球心向量上的分速度投影
let speed_ball1_initial_tc = speed_ball1_initial.dotProduct(t)/t.length();
let speed_ball2_initial_tc = speed_ball2_initial.dotProduct(t)/t.length();
// 碰撞后球心方向上的分速度
let speed_ball1_final_sc = (speed_ball1_initial_sc * (ball1.density * Math.pow(ball1.radius,3) - ball2.density * Math.pow(ball2.radius,3)) + 2 * (ball2.density * Math.pow(ball2.radius,3)) * speed_ball2_initial_sc)
/ (ball1.density * Math.pow(ball1.radius,3) + ball2.density * Math.pow(ball2.radius,3));
let speed_ball2_final_sc = (speed_ball2_initial_sc * (ball2.density * Math.pow(ball2.radius,3) - ball1.density * Math.pow(ball1.radius,3)) + 2 * (ball1.density * Math.pow(ball1.radius,3)) * speed_ball1_initial_sc)
/ (ball1.density * Math.pow(ball1.radius,3) + ball2.density * Math.pow(ball2.radius,3));
// 碰撞后球心方向上的分速度向量
let speed_ball1_final_s = s.scale(speed_ball1_final_sc);
let speed_ball2_final_s = s.scale(speed_ball2_final_sc);
// 碰撞后垂直球心方向上的分速度向量
let speed_ball1_final_t = t.scale(speed_ball1_initial_tc);
let speed_ball2_final_t = t.scale(speed_ball2_initial_tc);
// 結(jié)束速度向量
let speed_ball1_final = speed_ball1_final_s.add(speed_ball1_final_t);
let speed_ball2_final = speed_ball2_final_s.add(speed_ball2_final_t);
// 更新速度
ball1.speedX = speed_ball1_final.x;
ball1.speedY = speed_ball1_final.y;
ball2.speedX = speed_ball2_final.x;
ball2.speedY = speed_ball2_final.y;
});
}
缺陷
本代碼是通過(guò)window.requestAnimationFrame()
方法循環(huán)執(zhí)行來(lái)實(shí)現(xiàn)動(dòng)畫(huà)效果的诵冒,它的回調(diào)次數(shù)是每秒60次凯肋,所以對(duì)于一些速度"過(guò)快"的小球,會(huì)在撞擊邊界時(shí)出現(xiàn)"撞出去一小部分"的情況汽馋。還有本代碼只考慮了兩個(gè)小球相撞的情況侮东,沒(méi)有考慮三個(gè)以上小球同時(shí)相撞的場(chǎng)景。
結(jié)語(yǔ)
本代碼是學(xué)習(xí)JavaScript
時(shí)的實(shí)戰(zhàn)演練豹芯,能加深對(duì)這門(mén)語(yǔ)言的理解和掌握悄雅。完整代碼詳見(jiàn)GitHub地址。
參考鏈接
https://www.lyblog.net/detail/397.html
http://www.cnblogs.com/kenkofox/archive/2011/09/06/2168944.html
http://tina0152.blog.163.com/blog/static/119447958200910229109326/
http://www.51testing.com/html/66/n-861166-2.html