該算法適合常見的二維完全彈性碰撞的場合,支持對心碰撞(正碰)和非對心碰撞(斜碰),不考慮碰撞過程中的機械能損耗,不考慮物體表面摩擦以及恢復系數。
/*
* this是自身對象,sp是碰撞的對象
* this.m 質量
* this.r 半徑
* this.vx 水平速度
* this.vy 豎直速度
* 為了便於理解,代碼未經優化!
*/
collide(sp) {
if (this.isCollideWith(sp)) {
// 利用彈性碰撞公式計算水平、豎直方向上的速度分量 let vx = this.vx let vy = this.vy this.vx = ((this.m - sp.m) * this.vx + 2 * sp.m * sp.vx) / (this.m + sp.m) this.vy = ((this.m - sp.m) * this.vy + 2 * sp.m * sp.vy) / (this.m + sp.m) sp.vx = (2 * this.m * vx + (sp.m - this.m) * sp.vx) / (this.m + sp.m) sp.vy = (2 * this.m * vy + (sp.m - this.m) * sp.vy) / (this.m + sp.m)
// 關鍵!在速度突變的情況下(例如碰壁反彈,或者另一物體被擠壓以至於無法移動),必須防止位置重疊的情況出現
// nextXPos和nextYPos是提前判斷二者下一幀的位置。若下一幀位置重疊,則反彈。 let ax = this.nextXPos() let ay = this.nextYPos() let bx = sp.nextXPos() let by = sp.nextYPos() if (Math.sqrt((ax - bx) ** 2 + (ay - by) ** 2) < (this.r + sp.r)) { let agl = 0 // 反彈方向相對屏幕坐標系的角度 if (this.x != sp.x) agl = Math.atan((this.y - sp.y) / (this.x - sp.x))
// 反彈的速度的方向需根據二者位置來確定 let v = Math.sqrt(this.vx ** 2 + this.vy ** 2) this.vx = v * Math.cos(agl) * (this.x > sp.x ? 1 : -1) this.vy = v * Math.sin(agl) * (this.y > sp.y ? 1 : -1) v = Math.sqrt(sp.vx ** 2 + sp.vy ** 2) sp.vx = v * Math.cos(agl) * (sp.x > this.x ? 1 : -1) sp.vy = v * Math.sin(agl) * (sp.y > this.y ? 1 : -1)} }