來解決一下先前邊界反彈的問題,之前只是搪塞地實作,其實並不精確,為什麼呢?以右邊界為例,先前的做法是在球已經穿透邊界時…
套用一個反作用力,這時會讓 x 方向的速度方向相反,然後馬上呼叫 Body
的 update
將之移動:
接著在下次 draw
時就會新的速度向量來計算,這種做法乍看是也蠻像反彈的,不過實際上還是發生穿透了,有沒有更好的方式呢?先看看只有一個點好了,如果下個點會穿透邊界:
那麼找出下個點基於邊界的對稱點:
這樣不就是反彈了?
影格在時間上是不連續的,牆與點真正的碰撞時間是發生在影格之間,在下個影格時,能看到的只會是上圖的狀況。
如果是個圓呢?如果現在圓邊緣穿透邊界了:
實際上碰撞右邊界的會是圓最右邊的位置,因此上圖標示出圓的最右邊位置,然後碰撞應該是發生在下圖的狀況:
將上圖灰色的點基於邊界找出對稱點,該點就是反彈後的圓最右邊的點:
將以上的過程整理一下,箭頭虛線放在圓心就會是:
同樣地,牆與圓真正的碰撞時間是發生在影格之間,灰色圓的時機點實際上你看不到,在下個影格你能看到的就只是圖中反彈後的狀態。
對於方形的邊界,要根據計算出圓的最上、下、左、右等點很簡單,接著按照半徑位移一下,就是圓心了,這部份就自己算吧!
就簡單的方形邊界來說,〈牛頓運動定律〉中套用反作用力來反彈,其實是有點多此一舉,來將〈牛頓運動定律〉檢查邊界的 checkEdges
修改一下:
function checkEdges(body) {
const {x, y} = body.coordinate;
if(x + r > width) {
const nx = 2 * width - x - 2 * r;
body.coordinate.x = nx;
body.velocity.mult([-1, 1]);
}
if(x - r < 0) {
const nx = 2 * r - x;
body.coordinate.x = nx;
body.velocity.mult([-1, 1]);
}
if(y + r > height) {
const ny = 2 * height - y - 2 * r;
body.coordinate.y = ny;
body.velocity.mult([1, -1]);
}
if(y - r < 0) {
const ny = 2 * r - y;
body.coordinate.y = ny;
body.velocity.mult([1, -1]);
}
}
這個 checkEdges
接受的是 Body
實例,實際上計算的對象形狀是圓,為了更明確一些,來定義一個 Shape
與 Circle
好了:
class Shape {
constructor(body) {
this.body = body;
}
update() {
this.body.update();
}
}
class Circle extends Shape {
constructor(body, radius) {
super(body);
this.radius = radius;
}
draw() {
circle(this.body.coordinate.x, this.body.coordinate.y, 2 * this.radius);
}
}
Shape
實例會包裹 Body
實例,Circle
是一種 Shape
,只要是 Shape
就會有 draw
方法將自身畫出來,不過 JavaScript 沒有定義抽象方法這類語法,這邊就選擇只在 Circle
實作就可以了。
這麼一來,checkEdges
就可以修改為:
function checkEdges(c) {
const r = c.radius;
const body = c.body;
const {x, y} = body.coordinate;
if(x + r > width) {
const nx = 2 * width - x - 2 * r;
body.coordinate.x = nx;
body.velocity.mult([-1, 1]);
}
if(x - r < 0) {
const nx = 2 * r - x;
body.coordinate.x = nx;
body.velocity.mult([-1, 1]);
}
if(y + r > height) {
const ny = 2 * height - y - 2 * r;
body.coordinate.y = ny;
body.velocity.mult([1, -1]);
}
if(y - r < 0) {
const ny = 2 * r - y;
body.coordinate.y = ny;
body.velocity.mult([1, -1]);
}
}
〈牛頓運動定律〉中的反彈範例,現在可以修改為基於 Circle
: