Collision response circle does not work as expected

I am working on a HTML Canvas demo to learn more about the circle for detecting and responding to a collision. I believe the detection code is correct, but the math of the answer is not quite there.

The demo was implemented using TypeScript, which is a typed superset of JavaScript that translates to regular JavaScript.

I believe that the problem exists in the checkCollision method of the Circle class, in particular the math for calculating the new speed.

The position of the blue circle is controlled by the mouse (using the event listener). If the red circle collides on the right side of the blue circle, the response to the collision seems to work correctly, but if it approaches the left, it does not respond correctly.

I am looking for some tips on how I can revise the math of checkCollision to properly handle collisions from any angle.

Here is CodePen for the live demo and dev environment: CodePen

class DemoCanvas {
    canvasWidth: number = 500;
    canvasHeight: number = 500;
    canvas: HTMLCanvasElement = document.createElement('canvas');
    constructor() {
        this.canvas.width = this.canvasWidth;
        this.canvas.height = this.canvasHeight;
        this.canvas.style.border = '1px solid black';
        this.canvas.style.position = 'absolute';
        this.canvas.style.left = '50%';
        this.canvas.style.top = '50%';
        this.canvas.style.transform = 'translate(-50%, -50%)';
        document.body.appendChild(this.canvas);
    }

    clear() {
        this.canvas.getContext('2d').clearRect(0, 0, this.canvas.width, this.canvas.height);
    }

    getContext(): CanvasRenderingContext2D {
        return this.canvas.getContext('2d');
    }

    getWidth(): number {
        return this.canvasWidth;
    }

    getHeight(): number {
        return this.canvasHeight;
    }

    getTop(): number {
        return this.canvas.getBoundingClientRect().top;
    }

    getRight(): number {
        return this.canvas.getBoundingClientRect().right;
    }

    getBottom(): number {
        return this.canvas.getBoundingClientRect().bottom;
    }    

    getLeft(): number {
        return this.canvas.getBoundingClientRect().left;
    }
}

class Circle {
    x: number;
    y: number;
    xVelocity: number;
    yVelocity: number;
    radius: number;
    color: string;
    canvas: DemoCanvas;
    context: CanvasRenderingContext2D;

    constructor(x: number, y: number, xVelocity: number, yVelocity: number, color: string, gameCanvas: DemoCanvas) {
        this.radius = 20;
        this.x = x;
        this.y = y;
        this.xVelocity = xVelocity;
        this.yVelocity = yVelocity;
        this.color = color;
        this.canvas = gameCanvas;
        this.context = this.canvas.getContext();
    }

    public draw(): void {
        this.context.fillStyle = this.color;
        this.context.beginPath();
        this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
        this.context.fill();
    }

    public move(): void {
        this.x += this.xVelocity;
        this.y += this.yVelocity;
    }

    checkWallCollision(gameCanvas: DemoCanvas): void {
        let top = 0;
        let right = 500;
        let bottom = 500;
        let left = 0;

        if(this.y < top + this.radius) {
            this.y = top + this.radius;
            this.yVelocity *= -1;
        }

        if(this.x > right - this.radius) {
            this.x = right - this.radius;
            this.xVelocity *= -1;
        }

        if(this.y > bottom - this.radius) {
            this.y = bottom - this.radius;
            this.yVelocity *= -1;
        }

        if(this.x < left + this.radius) {
            this.x = left + this.radius;
            this.xVelocity *= -1;
        }
    }

    checkCollision(x1: number, y1: number, r1: number, x2: number, y2: number, r2: number) {
        let distance: number = Math.abs((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
        // Detect collision
        if(distance < (r1 + r2) * (r1 + r2)) {
            // Respond to collision
            let newVelocityX1 = (circle1.xVelocity + circle2.xVelocity) / 2;
            let newVelocityY1 = (circle1.yVelocity + circle1.yVelocity) / 2;

            circle1.x = circle1.x + newVelocityX1;
            circle1.y = circle1.y + newVelocityY1;

            circle1.xVelocity = newVelocityX1;
            circle1.yVelocity = newVelocityY1;
        }
    }
}

let demoCanvas = new DemoCanvas();
let circle1: Circle = new Circle(250, 250, 5, 5, "#F77", demoCanvas);
let circle2: Circle = new Circle(250, 540, 5, 5, "#7FF", demoCanvas);
addEventListener('mousemove', function(e) {
    let mouseX = e.clientX - demoCanvas.getLeft();
    let mouseY = e.clientY - demoCanvas.getTop();
    circle2.x = mouseX;
    circle2.y = mouseY;
});

function loop() {
    demoCanvas.clear();
    circle1.draw();
    circle2.draw();
    circle1.move();
    circle1.checkWallCollision(demoCanvas);
    circle2.checkWallCollision(demoCanvas);
    circle1.checkCollision(circle1.x, circle1.y, circle1.radius, circle2.x, circle2.y, circle2.radius);
    requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
+4
source share
2 answers

Elastic two-dimensional collision

The problem is probably due to the fact that the balls do not depart from each other, and then in the next frame they still overlap, and this gets worse. I think I'm just looking at the code.

A simple solution.

Before you can change the direction of the two balls, you must make sure that they are correctly positioned. They should just be touching (no overlap), or they may fall into each other.

.

   // note I am using javascript.
   // b1,b2 are the two balls or circles
   // b1.dx,b1.dy are velocity (deltas) to save space same for b2


   // get dist between them
   // first vect from one to the next
   const dx = b2.x - b1.x;
   const dy = b2.y - b1.y;

   // then distance
   const dist = Math.sqrt(dx*dx + dy*dy);

   // then check overlap
   if(b1.radius + b2.radius >= dist){ // the balls overlap
        // normalise the vector between them
         const nx = dx / dist;
         const ny = dy / dist;

         // now move each ball away from each other 
         // along the same line as the line between them
         // Use the ratio of the radius to work out where they touch
         const touchDistFromB1 = (dist * (b1.radius / (b1.radius + b2.radius)))         
         const contactX = b1.x + nx * touchDistFromB1;
         const contactY = b1.y + ny * touchDistFromB1;

         // now move each ball so that they just touch
         // move b1 back
         b1.x = contactX - nx * b1.radius;
         b1.y = contactY - ny * b1.radius;

         // and b2 in the other direction
         b2.x = contactX + nx * b2.radius;
         b2.y = contactY + ny * b2.radius;

, .

// from contact test for b1 is immovable
if(b1.radius + b2.radius >= dist){ // the balls overlap
    // normalise the vector between them
     const nx = dx / dist;
     const ny = dy / dist;

     // move b2 away from b1 along the contact line the distance of the radius summed
     b2.x = b1.x + nx * (b1.radius + b2.radius);
     b2.y = b1.y + ny * (b1.radius + b2.radius);

,

.

, , , - . , wiki .

.

, ,

 // get the direction and velocity of each ball
 const v1 = Math.sqrt(b1.dx * b1.dx + b1.dy * b1.dy);
 const v2 = Math.sqrt(b2.dx * b2.dx + b2.dy * b2.dy);

 // get the direction of travel of each ball
 const dir1 = Math.atan2(b1.dy, b1.dx);
 const dir2 = Math.atan2(b2.dy, b2.dx);

 // get the direction from ball1 center to ball2 cenet
 const directOfContact = Math.atan2(ny, nx);

 // You will also need a mass. You could use the area of a circle, or the
 // volume of a sphere to get the mass of each ball with its radius
 // this will make them react more realistically
 // An approximation is good as it is the ratio not the mass that is important
 // Thus ball are spheres. Volume is the cubed radius
 const mass1 = Math.pow(b1.radius,3);
 const mass1 = Math.pow(b2.radius,3);

, ,

 ellastic2DCollistionD(b1, b2, v1, v2, d1, d2, directOfContact, mass1, mass2);

.

 b1.x += b1.dx;
 b1.y += b1.dy;
 b2.x += b1.dx;
 b2.y += b1.dy;

, .

2D-

wiki

// obj1, obj2 are the object that will have their deltas change
// velocity1, velocity2 is the velocity of each
// dir1, dir2 is the direction of travel
// contactDir is the direction from the center of the first object to the center of the second.
// mass1, mass2 is the mass of the first and second objects.
//
// function ellastic2DCollistionD(obj1, obj2, velocity1, velocity2, dir1, dir2, contactDir, mass1, mass2){
// The function applies the formula below twice, once fro each object, allowing for a little optimisation.


// The formula of each object new velocity is 
//
// For 2D moving objects
// v1,v2 is velocity  
// m1, m2 is the mass 
// d1 , d2 us the direction of moment
// p is the angle of contact; 
//
//      v1* cos(d1-p) * (m1 - m2) + 2 * m2 * v2 * cos(d2- p)
// vx = ----------------------------------------------------- * cos(p) + v1 * sin(d1-p) * cos(p + PI/2)
//                    m1 + m2

//      v1* cos(d1-p) * (m1 - m2) + 2 * m2 * v2 * cos(d2- p)
// vy = ----------------------------------------------------- * sin(p) + v1 * sin(d1-p) * sin(p + PI/2)
//                     m1 + m2

// More info can be found at https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional

// to keep the code readable I use abbreviated names
function ellastic2DCollistionD(obj1, obj2, v1, v2, d1, d2, cDir, m1, m2){

    const mm = m1 - m2;
    const mmt = m1 + m2;
    const v1s = v1 * Math.sin(d1 - cDir);

    const cp = Math.cos(cDir);
    const sp = Math.sin(cDir);
    var cdp1 = v1 * Math.cos(d1 - cDir);
    var cdp2 = v2 * Math.cos(d2 - cDir);
    const cpp = Math.cos(cDir + Math.PI / 2)
    const spp = Math.sin(cDir + Math.PI / 2)

    var t = (cdp1 * mm + 2 * m2 * cdp2) / mmt;
    obj1.dx = t * cp + v1s * cpp;
    obj1.dy = t * sp + v1s * spp;
    cDir += Math.PI;
    const v2s = v2 * Math.sin(d2 - cDir);    
    cdp1 = v1 * Math.cos(d1 - cDir);
    cdp2 = v2 * Math.cos(d2 - cDir);    
    t = (cdp2 * -mm + 2 * m1 * cdp1) / mmt;
    obj2.dx = t * -cp + v2s * -cpp;
    obj2.dy = t * -sp + v2s * -spp;
}

, typeScript, - . obj1, obj2 .

typeScript.

0

, .

(, , jsfiddle fooobar.com/questions/1684582/...).

circle2 , , , - . .

requestAnimationFrame, , , . ( , 1), . , , , , , .

+2

Source: https://habr.com/ru/post/1684581/


All Articles