邊界反彈


來解決一下先前邊界反彈的問題,之前只是搪塞地實作,其實並不精確,為什麼呢?以右邊界為例,先前的做法是在球已經穿透邊界時…

邊界反彈

套用一個反作用力,這時會讓 x 方向的速度方向相反,然後馬上呼叫 Bodyupdate 將之移動:

邊界反彈

接著在下次 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 實例,實際上計算的對象形狀是圓,為了更明確一些,來定義一個 ShapeCircle 好了:

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