JavaScript學(xué)習(xí)筆記之多小球非對(duì)心彈性碰撞

引言

在學(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铁蹈,一起剝皮案震驚了整個(gè)濱河市煤伟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌木缝,老刑警劉巖便锨,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異我碟,居然都是意外死亡放案,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)矫俺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吱殉,“玉大人,你說(shuō)我怎么就攤上這事厘托∮仰ǎ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵铅匹,是天一觀(guān)的道長(zhǎng)押赊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)包斑,這世上最難降的妖魔是什么流礁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮罗丰,結(jié)果婚禮上神帅,老公的妹妹穿的比我還像新娘。我一直安慰自己萌抵,他們只是感情好找御,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布元镀。 她就那樣靜靜地躺著,像睡著了一般霎桅。 火紅的嫁衣襯著肌膚如雪凹联。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天哆档,我揣著相機(jī)與錄音,去河邊找鬼住闯。 笑死瓜浸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的比原。 我是一名探鬼主播插佛,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼量窘!你這毒婦竟也來(lái)了雇寇?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蚌铜,失蹤者是張志新(化名)和其女友劉穎锨侯,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體冬殃,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡囚痴,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了审葬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片深滚。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涣觉,靈堂內(nèi)的尸體忽然破棺而出痴荐,到底是詐尸還是另有隱情,我是刑警寧澤官册,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布生兆,位于F島的核電站,受9級(jí)特大地震影響膝宁,放射性物質(zhì)發(fā)生泄漏皂贩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一昆汹、第九天 我趴在偏房一處隱蔽的房頂上張望明刷。 院中可真熱鬧,春花似錦满粗、人聲如沸辈末。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挤聘。三九已至轰枝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間组去,已是汗流浹背鞍陨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留从隆,地道東北人诚撵。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像键闺,于是被迫代替她去往敵國(guó)和親寿烟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345