LukzzXB
LukzzXB

Reputation: 57

Collision detection, control by mouse

i have a simple physics engine to detect collisions between circles and paddles, right now i can set the paddle to be controlled by the mouse or not. but i want to be able to control one of the circles by mouse instead. at the bottom where bat[i].update() will control the specified paddle by that index. but i want to be able to control the circle by the mouse instead to further expand this engine. is there any way i can do this?

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext("2d");
const mouse = { x: 0, y: 0, button: false }

function mouseEvents(e) {
    mouse.x = e.pageX;
    mouse.y = e.pageY;
    mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));

// short cut vars 
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center 
var ch = h / 2;
const gravity = 0;
var balls = []
var bats = []

// constants and helpers
const PI2 = Math.PI * 2;
const setStyle = (ctx, style) => { Object.keys(style).forEach(key => ctx[key] = style[key]) };

function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function distance(x1, y1, x2, y2) {
    const xDist = x2 - x1;
    const yDist = y2 - y1;
    return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
}

// the ball
class ball {
    constructor() {
        this.r = 25
        this.x = random(50, 1500)
        this.y = random(50, 1500)
        this.dx = 15
        this.dy = 15
        this.mass = 1
        this.maxSpeed = 15
        this.style = {
            lineWidth: 12,
            strokeStyle: "green"
        }
    }
    draw(ctx) {
        setStyle(ctx, this.style);
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2);
        ctx.stroke();
    }
    update() {
        this.dy += gravity;
        var speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy);
        var x = this.x + this.dx;
        var y = this.y + this.dy;

        if (y > canvas.height - this.r) {
            y = (canvas.height - this.r) - (y - (canvas.height - this.r));
            this.dy = -this.dy;
        }
        if (y < this.r) {
            y = this.r - (y - this.r);
            this.dy = -this.dy;
        }
        if (x > canvas.width - this.r) {
            x = (canvas.width - this.r) - (x - (canvas.width - this.r));
            this.dx = -this.dx;
        }
        if (x < this.r) {
            x = this.r - (x - this.r);
            this.dx = -this.dx;
        }
        this.x = x;
        this.y = y;
        if (speed > this.maxSpeed) { // if over speed then slow the ball down gradualy
            var reduceSpeed = this.maxSpeed + (speed - this.maxSpeed) * 0.9; // reduce speed if over max speed
            this.dx = (this.dx / speed) * reduceSpeed;
            this.dy = (this.dy / speed) * reduceSpeed;
        }
        for (var i = 0; i < balls.length; i++) {
            if (this === balls[i]) continue
            if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) {
                resolveCollision(this, balls[i])
            }
        }

    }
}
class player {
    constructor() {
        this.r = 50
        this.x = random(50, 1500)
        this.y = random(50, 1500)
        this.dx = 0.2
        this.dy = 0.2
        this.mass = 1
        this.maxSpeed = 1000
        this.style = {
            lineWidth: 12,
            strokeStyle: "blue"
        }
    }
    draw(ctx) {
        setStyle(ctx, this.style);
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2);
        ctx.stroke();
    }
    update() {
        this.dx = mouse.x - this.x;
        this.dy = mouse.y - this.y;
        var x = this.x + this.dx;
        var y = this.y + this.dy;
        x < this.width / 2 && (x / 2);
        y < this.height / 2 && (y / 2);
        x > canvas.width / 2 && (x = canvas.width / 2);
        y > canvas.height / 2 && (y = canvas.height / 2);
        this.dx = x - this.x;
        this.dy = y - this.y;
        this.x = x;
        this.y = y;
        for (var i = 0; i < balls.length; i++) {
            if (this === balls[i]) continue
            if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) {
                resolveCollision(this, balls[i])
            }
        }

    }
}
const ballShadow = { 
        r: 50,
        x: 50,
        y: 50,
        dx: 0.2,
        dy: 0.2,
    }
//bat
class bat {
    constructor(x, y, w, h) {
        this.x = x
        this.y = y
        this.dx = 0
        this.dy = 0
        this.width = w
        this.height = h
        this.style = {
            lineWidth: 2,
            strokeStyle: "black",
        }
    }
    draw(ctx) {
        setStyle(ctx, this.style);
        ctx.strokeRect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);
    }

    update() {
        this.dx = mouse.x - this.x;
        this.dy = mouse.y - this.y;
        var x = this.x + this.dx;
        var y = this.y + this.dy;
        x < this.width / 2 && (x = this.width / 2);
        y < this.height / 2 && (y = this.height / 2);
        x > canvas.width - this.width / 2 && (x = canvas.width - this.width / 2);
        y > canvas.height - this.height / 2 && (y = canvas.height - this.height / 2);
        this.dx = x - this.x;
        this.dy = y - this.y;
        this.x = x;
        this.y = y;

    }
}

function doBatBall(bat, ball) {
    var mirrorX = 1;
    var mirrorY = 1;

    const s = ballShadow; // alias
    s.x = ball.x;
    s.y = ball.y;
    s.dx = ball.dx;
    s.dy = ball.dy;
    s.x -= s.dx;
    s.y -= s.dy;

    // get the bat half width height
    const batW2 = bat.width / 2;
    const batH2 = bat.height / 2;

    // and bat size plus radius of ball
    var batH = batH2 + ball.r;
    var batW = batW2 + ball.r;

    // set ball position relative to bats last pos
    s.x -= bat.x;
    s.y -= bat.y;

    // set ball delta relative to bat
    s.dx -= bat.dx;
    s.dy -= bat.dy;

    // mirror x and or y if needed
    if (s.x < 0) {
        mirrorX = -1;
        s.x = -s.x;
        s.dx = -s.dx;
    }
    if (s.y < 0) {
        mirrorY = -1;
        s.y = -s.y;
        s.dy = -s.dy;
    }


    // bat now only has a bottom, right sides and bottom right corner
    var distY = (batH - s.y); // distance from bottom 
    var distX = (batW - s.x); // distance from right

    if (s.dx > 0 && s.dy > 0) { return } // ball moving away so no hit

    var ballSpeed = Math.sqrt(s.dx * s.dx + s.dy * s.dy) // get ball speed relative to bat

    // get x location of intercept for bottom of bat
    var bottomX = s.x + (s.dx / s.dy) * distY;

    // get y location of intercept for right of bat
    var rightY = s.y + (s.dy / s.dx) * distX;

    // get distance to bottom and right intercepts
    var distB = Math.hypot(bottomX - s.x, batH - s.y);
    var distR = Math.hypot(batW - s.x, rightY - s.y);
    var hit = false;

    if (s.dy < 0 && bottomX <= batW2 && distB <= ballSpeed && distB < distR) { // if hit is on bottom and bottom hit is closest
        hit = true;
        s.y = batH - s.dy * ((ballSpeed - distB) / ballSpeed);
        s.dy = -s.dy;
    }
    if (!hit && s.dx < 0 && rightY <= batH2 && distR <= ballSpeed && distR <= distB) { // if hit is on right and right hit is closest
        hit = true;
        s.x = batW - s.dx * ((ballSpeed - distR) / ballSpeed);;
        s.dx = -s.dx;
    }
    if (!hit) { // if no hit may have intercepted the corner. 
        // find the distance that the corner is from the line segment from the balls pos to the next pos
        const u = ((batW2 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed);

        // get the closest point on the line to the corner
        var cpx = s.x + s.dx * u;
        var cpy = s.y + s.dy * u;

        // get ball radius squared
        const radSqr = ball.r * ball.r;

        // get the distance of that point from the corner squared
        const dist = (cpx - batW2) * (cpx - batW2) + (cpy - batH2) * (cpy - batH2);

        // is that distance greater than ball radius
        if (dist > radSqr) { return } // no hit

        // solves the triangle from center to closest point on balls trajectory
        var d = Math.sqrt(radSqr - dist) / ballSpeed;

        // intercept point is closest to line start
        cpx -= s.dx * d;
        cpy -= s.dy * d;

        // get the distance from the ball current pos to the intercept point
        d = Math.hypot(cpx - s.x, cpy - s.y);

        // is the distance greater than the ball speed then its a miss
        if (d > ballSpeed) { return } // no hit return

        s.x = cpx; // position of contact
        s.y = cpy;

        // find the normalised tangent at intercept point 
        const ty = (cpx - batW2) / ball.r;
        const tx = -(cpy - batH2) / ball.r;

        // calculate the reflection vector
        const bsx = s.dx / ballSpeed; // normalise ball speed
        const bsy = s.dy / ballSpeed;
        const dot = (bsx * tx + bsy * ty) * 2;

        // get the distance the ball travels past the intercept
        d = ballSpeed - d;

        // the reflected vector is the balls new delta (this delta is normalised)
        s.dx = (tx * dot - bsx);
        s.dy = (ty * dot - bsy);

        // move the ball the remaining distance away from corner
        s.x += s.dx * d;
        s.y += s.dy * d;

        // set the ball delta to the balls speed
        s.dx *= ballSpeed
        s.dy *= ballSpeed
        hit = true;
    }

    // if the ball hit the bat restore absolute position
    if (hit) {
        // reverse mirror
        s.x *= mirrorX;
        s.dx *= mirrorX;
        s.y *= mirrorY;
        s.dy *= mirrorY;

        // remove bat relative position
        s.x += bat.x;
        s.y += bat.y;

        // remove bat relative delta
        s.dx += bat.dx;
        s.dy += bat.dy;

        // set the balls new position and delta
        ball.x = s.x;
        ball.y = s.y;
        ball.dx = s.dx;
        ball.dy = s.dy;
    }
}

function rotate(velocity, angle) {
    const rotatedVelocities = {
        x: velocity.x * Math.cos(angle) + velocity.y * Math.sin(angle),
        y: -velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle)
    };

    return rotatedVelocities;
}

function resolveCollision(particle, otherParticle) {
    const xVelocityDiff = particle.dx - otherParticle.dx;
    const yVelocityDiff = particle.dy - otherParticle.dy;

    const xDist = otherParticle.x - particle.x;
    const yDist = otherParticle.y - particle.y;

    if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) {

        const angle = Math.atan2(otherParticle.y - particle.y, otherParticle.x - particle.x);
        const m1 = particle.mass;
        const m2 = otherParticle.mass;

        const u1 = rotate({ x: particle.dx, y: particle.dy }, angle);
        const u2 = rotate({ x: otherParticle.dx, y: otherParticle.dy }, angle);

        const v1 = {
            x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2),
            y: u1.y
        };
        const v2 = {
            x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2),

            y: u2.y
        };

        const vFinal1 = rotate(v1, -angle);
        const vFinal2 = rotate(v2, -angle);

        particle.dx = vFinal1.x;
        particle.dy = vFinal1.y;

        otherParticle.dx = vFinal2.x;
        otherParticle.dy = vFinal2.y;
    }
}

for (let i = 0; i < 10; i++) {
    balls.push(new ball())
}
balls.push(new player())
    //x,y,w,h
bats.push(new bat((window.innerWidth / 2) - 150, window.innerHeight / 2, 10, 100))
bats.push(new bat((window.innerWidth / 2) + 150, window.innerHeight / 2, 10, 100))

// main update function
function update(timer) {

    if (w !== innerWidth || h !== innerHeight) {
        cw = (w = canvas.width = innerWidth) / 2;
        ch = (h = canvas.height = innerHeight) / 2;
    }



    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    ctx.globalAlpha = 1; // reset alpha
    ctx.clearRect(0, 0, w, h);

    // move bat and ball
    for (var i = 0; i < balls.length; i++) {
        for (var j = 0; j < bats.length; j++) {
            doBatBall(bats[j], balls[i])
        }
        balls[i].update()
        balls[i].draw(ctx)
    }
    bats.forEach(bat => {
        //bat.update();
        bat.draw(ctx);
    })

    // check for bal bat contact and change ball position and trajectory if needed

    // draw ball and bat

    requestAnimationFrame(update);

}
requestAnimationFrame(update);
<body>
    <canvas></canvas>
</body>

Upvotes: 2

Views: 482

Answers (2)

Justin
Justin

Reputation: 2958

Adding your player to the array of balls is not what you want to do. Keep that object separate. Create your player as a variable and in your loop just draw and update the player

let player = new Player();

function update(timer) {
  ...
player.update();
player.draw(ctx);
  ...
}

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext("2d");
const mouse = { x: 0, y: 0, button: false }

function mouseEvents(e) {
    mouse.x = e.x;
    mouse.y = e.y;
    mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));

// short cut vars 
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center 
var ch = h / 2;
const gravity = 0;
var balls = []
var bats = []

// constants and helpers
const PI2 = Math.PI * 2;
const setStyle = (ctx, style) => { Object.keys(style).forEach(key => ctx[key] = style[key]) };

function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function distance(x1, y1, x2, y2) {
    const xDist = x2 - x1;
    const yDist = y2 - y1;
    return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
}

// the ball
class ball {
    constructor() {
        this.r = 25
        this.x = random(50, 1500)
        this.y = random(50, 1500)
        this.dx = 15
        this.dy = 15
        this.mass = 1
        this.maxSpeed = 15
        this.style = {
            lineWidth: 12,
            strokeStyle: "green"
        }
    }
    draw(ctx) {
        setStyle(ctx, this.style);
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2);
        ctx.stroke();
    }
    update() {
        this.dy += gravity;
        var speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy);
        var x = this.x + this.dx;
        var y = this.y + this.dy;

        if (y > canvas.height - this.r) {
            y = (canvas.height - this.r) - (y - (canvas.height - this.r));
            this.dy = -this.dy;
        }
        if (y < this.r) {
            y = this.r - (y - this.r);
            this.dy = -this.dy;
        }
        if (x > canvas.width - this.r) {
            x = (canvas.width - this.r) - (x - (canvas.width - this.r));
            this.dx = -this.dx;
        }
        if (x < this.r) {
            x = this.r - (x - this.r);
            this.dx = -this.dx;
        }
        this.x = x;
        this.y = y;
        if (speed > this.maxSpeed) { // if over speed then slow the ball down gradualy
            var reduceSpeed = this.maxSpeed + (speed - this.maxSpeed) * 0.9; // reduce speed if over max speed
            this.dx = (this.dx / speed) * reduceSpeed;
            this.dy = (this.dy / speed) * reduceSpeed;
        }
        for (var i = 0; i < balls.length; i++) {
            if (this === balls[i]) continue
            if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) {
                resolveCollision(this, balls[i])
            }
        }

    }
}
class Player {
    constructor() {
        this.r = 50
        this.x = random(50, 1500)
        this.y = random(50, 1500)
        this.dx = 0.2
        this.dy = 0.2
        this.mass = 1
        this.maxSpeed = 1000
        this.style = {
            lineWidth: 12,
            strokeStyle: "blue"
        }
    }
    draw(ctx) {
        setStyle(ctx, this.style);
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2);
        ctx.stroke();
    }
    update() {
        this.dx = mouse.x - this.x;
        this.dy = mouse.y - this.y;
        var x = this.x + this.dx;
        var y = this.y + this.dy;
        /*x < this.width / 2 && (x / 2);
        y < this.height / 2 && (y / 2);
        x > canvas.width / 2 && (x = canvas.width / 2);
        y > canvas.height / 2 && (y = canvas.height / 2);*/
        this.dx = x - this.x;
        this.dy = y - this.y;
        this.x = x;
        this.y = y;
        for (var i = 0; i < balls.length; i++) {
            //if (this === balls[i]) continue
            if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) {
                resolveCollision(this, balls[i])
            }
        }

    }
}
const ballShadow = { 
        r: 50,
        x: 50,
        y: 50,
        dx: 0.2,
        dy: 0.2,
    }
//bat
class bat {
    constructor(x, y, w, h) {
        this.x = x
        this.y = y
        this.dx = 0
        this.dy = 0
        this.width = w
        this.height = h
        this.style = {
            lineWidth: 2,
            strokeStyle: "black",
        }
    }
    draw(ctx) {
        setStyle(ctx, this.style);
        ctx.strokeRect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);
    }

    update() {
        this.dx = mouse.x - this.x;
        this.dy = mouse.y - this.y;
        var x = this.x + this.dx;
        var y = this.y + this.dy;
        x < this.width / 2 && (x = this.width / 2);
        y < this.height / 2 && (y = this.height / 2);
        x > canvas.width - this.width / 2 && (x = canvas.width - this.width / 2);
        y > canvas.height - this.height / 2 && (y = canvas.height - this.height / 2);
        this.dx = x - this.x;
        this.dy = y - this.y;
        this.x = x;
        this.y = y;

    }
}

function doBatBall(bat, ball) {
    var mirrorX = 1;
    var mirrorY = 1;

    const s = ballShadow; // alias
    s.x = ball.x;
    s.y = ball.y;
    s.dx = ball.dx;
    s.dy = ball.dy;
    s.x -= s.dx;
    s.y -= s.dy;

    // get the bat half width height
    const batW2 = bat.width / 2;
    const batH2 = bat.height / 2;

    // and bat size plus radius of ball
    var batH = batH2 + ball.r;
    var batW = batW2 + ball.r;

    // set ball position relative to bats last pos
    s.x -= bat.x;
    s.y -= bat.y;

    // set ball delta relative to bat
    s.dx -= bat.dx;
    s.dy -= bat.dy;

    // mirror x and or y if needed
    if (s.x < 0) {
        mirrorX = -1;
        s.x = -s.x;
        s.dx = -s.dx;
    }
    if (s.y < 0) {
        mirrorY = -1;
        s.y = -s.y;
        s.dy = -s.dy;
    }


    // bat now only has a bottom, right sides and bottom right corner
    var distY = (batH - s.y); // distance from bottom 
    var distX = (batW - s.x); // distance from right

    if (s.dx > 0 && s.dy > 0) { return } // ball moving away so no hit

    var ballSpeed = Math.sqrt(s.dx * s.dx + s.dy * s.dy) // get ball speed relative to bat

    // get x location of intercept for bottom of bat
    var bottomX = s.x + (s.dx / s.dy) * distY;

    // get y location of intercept for right of bat
    var rightY = s.y + (s.dy / s.dx) * distX;

    // get distance to bottom and right intercepts
    var distB = Math.hypot(bottomX - s.x, batH - s.y);
    var distR = Math.hypot(batW - s.x, rightY - s.y);
    var hit = false;

    if (s.dy < 0 && bottomX <= batW2 && distB <= ballSpeed && distB < distR) { // if hit is on bottom and bottom hit is closest
        hit = true;
        s.y = batH - s.dy * ((ballSpeed - distB) / ballSpeed);
        s.dy = -s.dy;
    }
    if (!hit && s.dx < 0 && rightY <= batH2 && distR <= ballSpeed && distR <= distB) { // if hit is on right and right hit is closest
        hit = true;
        s.x = batW - s.dx * ((ballSpeed - distR) / ballSpeed);;
        s.dx = -s.dx;
    }
    if (!hit) { // if no hit may have intercepted the corner. 
        // find the distance that the corner is from the line segment from the balls pos to the next pos
        const u = ((batW2 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed);

        // get the closest point on the line to the corner
        var cpx = s.x + s.dx * u;
        var cpy = s.y + s.dy * u;

        // get ball radius squared
        const radSqr = ball.r * ball.r;

        // get the distance of that point from the corner squared
        const dist = (cpx - batW2) * (cpx - batW2) + (cpy - batH2) * (cpy - batH2);

        // is that distance greater than ball radius
        if (dist > radSqr) { return } // no hit

        // solves the triangle from center to closest point on balls trajectory
        var d = Math.sqrt(radSqr - dist) / ballSpeed;

        // intercept point is closest to line start
        cpx -= s.dx * d;
        cpy -= s.dy * d;

        // get the distance from the ball current pos to the intercept point
        d = Math.hypot(cpx - s.x, cpy - s.y);

        // is the distance greater than the ball speed then its a miss
        if (d > ballSpeed) { return } // no hit return

        s.x = cpx; // position of contact
        s.y = cpy;

        // find the normalised tangent at intercept point 
        const ty = (cpx - batW2) / ball.r;
        const tx = -(cpy - batH2) / ball.r;

        // calculate the reflection vector
        const bsx = s.dx / ballSpeed; // normalise ball speed
        const bsy = s.dy / ballSpeed;
        const dot = (bsx * tx + bsy * ty) * 2;

        // get the distance the ball travels past the intercept
        d = ballSpeed - d;

        // the reflected vector is the balls new delta (this delta is normalised)
        s.dx = (tx * dot - bsx);
        s.dy = (ty * dot - bsy);

        // move the ball the remaining distance away from corner
        s.x += s.dx * d;
        s.y += s.dy * d;

        // set the ball delta to the balls speed
        s.dx *= ballSpeed
        s.dy *= ballSpeed
        hit = true;
    }

    // if the ball hit the bat restore absolute position
    if (hit) {
        // reverse mirror
        s.x *= mirrorX;
        s.dx *= mirrorX;
        s.y *= mirrorY;
        s.dy *= mirrorY;

        // remove bat relative position
        s.x += bat.x;
        s.y += bat.y;

        // remove bat relative delta
        s.dx += bat.dx;
        s.dy += bat.dy;

        // set the balls new position and delta
        ball.x = s.x;
        ball.y = s.y;
        ball.dx = s.dx;
        ball.dy = s.dy;
    }
}

function rotate(velocity, angle) {
    const rotatedVelocities = {
        x: velocity.x * Math.cos(angle) + velocity.y * Math.sin(angle),
        y: -velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle)
    };

    return rotatedVelocities;
}

function resolveCollision(particle, otherParticle) {
    const xVelocityDiff = particle.dx - otherParticle.dx;
    const yVelocityDiff = particle.dy - otherParticle.dy;

    const xDist = otherParticle.x - particle.x;
    const yDist = otherParticle.y - particle.y;

    if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) {

        const angle = Math.atan2(otherParticle.y - particle.y, otherParticle.x - particle.x);
        const m1 = particle.mass;
        const m2 = otherParticle.mass;

        const u1 = rotate({ x: particle.dx, y: particle.dy }, angle);
        const u2 = rotate({ x: otherParticle.dx, y: otherParticle.dy }, angle);

        const v1 = {
            x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2),
            y: u1.y
        };
        const v2 = {
            x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2),

            y: u2.y
        };

        const vFinal1 = rotate(v1, -angle);
        const vFinal2 = rotate(v2, -angle);

        particle.dx = vFinal1.x;
        particle.dy = vFinal1.y;

        otherParticle.dx = vFinal2.x;
        otherParticle.dy = vFinal2.y;
    }
}

for (let i = 0; i < 10; i++) {
    balls.push(new ball())
}
let player = new Player();
    //x,y,w,h
bats.push(new bat((window.innerWidth / 2) - 150, window.innerHeight / 2, 10, 100))
bats.push(new bat((window.innerWidth / 2) + 150, window.innerHeight / 2, 10, 100))

// main update function
function update(timer) {

    if (w !== innerWidth || h !== innerHeight) {
        cw = (w = canvas.width = innerWidth) / 2;
        ch = (h = canvas.height = innerHeight) / 2;
    }

    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    ctx.globalAlpha = 1; // reset alpha
    ctx.clearRect(0, 0, w, h);

    // move bat and ball
    for (var i = 1; i < balls.length; i++) {
        for (var j = 0; j < bats.length; j++) {
            doBatBall(bats[j], balls[i])
        }
        balls[i].update()
        balls[i].draw(ctx)
    }
    player.update();
    player.draw(ctx);
    bats.forEach(bat => {
        //bat.update();
        bat.draw(ctx);
    })

    // check for bal bat contact and change ball position and trajectory if needed

    // draw ball and bat

    requestAnimationFrame(update);

}
update();
 <canvas></canvas>

Keep in mind the mouse is not exactly in the center of the circle because you are not accounting for the canvas position in the mousemove function. Also you have something limiting your player objects ability to move over the whole canvas.

EDIT:

This is what was causing your player to not be able to move over the whole canvas so comment it out.

/* x < this.width / 2 && (x / 2);
y < this.height / 2 && (y / 2);
x > canvas.width / 2 && (x = canvas.width / 2);
y > canvas.height / 2 && (y = canvas.height / 2);*/

And your collision is slightly off because in you player class you are checking distance against this.r * 2. I would change it to

 for (var i = 0; i < balls.length; i++) {
            //if (this === balls[i]) continue //not really needed
            if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r + balls[i].r) {
                resolveCollision(this, balls[i])
            }

Upvotes: 1

vanowm
vanowm

Reputation: 10201

The balls cannot have 0 speed, but player can. Because of that, it fails at dividing by zero:

        const u = ((batW2 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed);

Simply add || 1 to the end of the line should fix it.

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext("2d");
const mouse = { x: 0, y: 0, button: false }

function mouseEvents(e) {
    mouse.x = e.pageX;
    mouse.y = e.pageY;
    mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));

// short cut vars 
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center 
var ch = h / 2;
const gravity = 0;
var balls = []
var bats = []

// constants and helpers
const PI2 = Math.PI * 2;
const setStyle = (ctx, style) => { Object.keys(style).forEach(key => ctx[key] = style[key]) };

function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function distance(x1, y1, x2, y2) {
    const xDist = x2 - x1;
    const yDist = y2 - y1;
    return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
}

// the ball
class ball {
    constructor() {
        this.r = 25
        this.x = random(50, 1500)
        this.y = random(50, 1500)
        this.dx = 15
        this.dy = 15
        this.mass = 1
        this.maxSpeed = 15
        this.style = {
            lineWidth: 12,
            strokeStyle: "green"
        }
    }
    draw(ctx) {
        setStyle(ctx, this.style);
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2);
        ctx.stroke();
    }
    update() {
        this.dy += gravity;
        var speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy);
        var x = this.x + this.dx;
        var y = this.y + this.dy;

        if (y > canvas.height - this.r) {
            y = (canvas.height - this.r) - (y - (canvas.height - this.r));
            this.dy = -this.dy;
        }
        if (y < this.r) {
            y = this.r - (y - this.r);
            this.dy = -this.dy;
        }
        if (x > canvas.width - this.r) {
            x = (canvas.width - this.r) - (x - (canvas.width - this.r));
            this.dx = -this.dx;
        }
        if (x < this.r) {
            x = this.r - (x - this.r);
            this.dx = -this.dx;
        }
        this.x = x;
        this.y = y;
        if (speed > this.maxSpeed) { // if over speed then slow the ball down gradualy
            var reduceSpeed = this.maxSpeed + (speed - this.maxSpeed) * 0.9; // reduce speed if over max speed
            this.dx = (this.dx / speed) * reduceSpeed;
            this.dy = (this.dy / speed) * reduceSpeed;
        }
        for (var i = 0; i < balls.length; i++) {
            if (this === balls[i]) continue
            if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) {
                resolveCollision(this, balls[i])
            }
        }

    }
}
class player {
    constructor() {
        this.r = 50
        this.x = random(50, 1500)
        this.y = random(50, 1500)
        this.dx = 0.2
        this.dy = 0.2
        this.mass = 1
        this.maxSpeed = 1000
        this.width = this.r
        this.height = this.r
        this.style = {
            lineWidth: 12,
            strokeStyle: "blue"
        }
    }
    draw(ctx) {
        setStyle(ctx, this.style);
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2);
        ctx.stroke();
    }
    update() {
        this.dx = mouse.x - this.x;
        this.dy = mouse.y - this.y;
        var x = this.x + this.dx;
        var y = this.y + this.dy;
/* change */
/*
        x < this.width / 2 && (x / 2);
        y < this.height / 2 && (y / 2);
        x > canvas.width / 2 && (x = canvas.width / 2);
        y > canvas.height / 2 && (y = canvas.height / 2);
*/
        x < this.width && (x = this.width);
        y < this.height && (y = this.height);
        x > canvas.width - this.width && (x = canvas.width - this.width);
        y > canvas.height - this.height && (y = canvas.height - this.height);
/* end change */
        this.dx = x - this.x;
        this.dy = y - this.y;
        this.x = x;
        this.y = y;
        for (var i = 0; i < balls.length; i++) {
            if (this === balls[i]) continue
            if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) {
                resolveCollision(this, balls[i])
            }
        }

    }
}
const ballShadow = { 
        r: 50,
        x: 50,
        y: 50,
        dx: 0.2,
        dy: 0.2,
    }
//bat
class bat {
    constructor(x, y, w, h) {
        this.x = x
        this.y = y
        this.dx = 0
        this.dy = 0
        this.width = w
        this.height = h
        this.style = {
            lineWidth: 2,
            strokeStyle: "black",
        }
    }
    draw(ctx) {
        setStyle(ctx, this.style);
        ctx.strokeRect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);
    }

    update() {
        this.dx = mouse.x - this.x;
        this.dy = mouse.y - this.y;
        var x = this.x + this.dx;
        var y = this.y + this.dy;
        x < this.width / 2 && (x = this.width / 2);
        y < this.height / 2 && (y = this.height / 2);
        x > canvas.width - this.width / 2 && (x = canvas.width - this.width / 2);
        y > canvas.height - this.height / 2 && (y = canvas.height - this.height / 2);
        this.dx = x - this.x;
        this.dy = y - this.y;
        this.x = x;
        this.y = y;

    }
}

function doBatBall(bat, ball) {
    var mirrorX = 1;
    var mirrorY = 1;

    const s = ballShadow; // alias
    s.x = ball.x;
    s.y = ball.y;
    s.dx = ball.dx;
    s.dy = ball.dy;
    s.x -= s.dx;
    s.y -= s.dy;

    // get the bat half width height
    const batW2 = bat.width / 2;
    const batH2 = bat.height / 2;

    // and bat size plus radius of ball
    var batH = batH2 + ball.r;
    var batW = batW2 + ball.r;

    // set ball position relative to bats last pos
    s.x -= bat.x;
    s.y -= bat.y;

    // set ball delta relative to bat
    s.dx -= bat.dx;
    s.dy -= bat.dy;

    // mirror x and or y if needed
    if (s.x < 0) {
        mirrorX = -1;
        s.x = -s.x;
        s.dx = -s.dx;
    }
    if (s.y < 0) {
        mirrorY = -1;
        s.y = -s.y;
        s.dy = -s.dy;
    }


    // bat now only has a bottom, right sides and bottom right corner
    var distY = (batH - s.y); // distance from bottom 
    var distX = (batW - s.x); // distance from right

    if (s.dx > 0 && s.dy > 0) { return } // ball moving away so no hit

    var ballSpeed = Math.sqrt(s.dx * s.dx + s.dy * s.dy) // get ball speed relative to bat

    // get x location of intercept for bottom of bat
    var bottomX = s.x + (s.dx / s.dy) * distY;

    // get y location of intercept for right of bat
    var rightY = s.y + (s.dy / s.dx) * distX;

    // get distance to bottom and right intercepts
    var distB = Math.hypot(bottomX - s.x, batH - s.y);
    var distR = Math.hypot(batW - s.x, rightY - s.y);
    var hit = false;

    if (s.dy < 0 && bottomX <= batW2 && distB <= ballSpeed && distB < distR) { // if hit is on bottom and bottom hit is closest
        hit = true;
        s.y = batH - s.dy * ((ballSpeed - distB) / ballSpeed);
        s.dy = -s.dy;
    }
    if (!hit && s.dx < 0 && rightY <= batH2 && distR <= ballSpeed && distR <= distB) { // if hit is on right and right hit is closest
        hit = true;
        s.x = batW - s.dx * ((ballSpeed - distR) / ballSpeed);;
        s.dx = -s.dx;
    }
    if (!hit) { // if no hit may have intercepted the corner. 
        // find the distance that the corner is from the line segment from the balls pos to the next pos
/* change */
//        const u = ((batW2 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed);
        const u = ((batW2 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed) || 1;
/* end change */
        // get the closest point on the line to the corner
        var cpx = s.x + s.dx * u;
        var cpy = s.y + s.dy * u;

        // get ball radius squared
        const radSqr = ball.r * ball.r;

        // get the distance of that point from the corner squared
        const dist = (cpx - batW2) * (cpx - batW2) + (cpy - batH2) * (cpy - batH2);

        // is that distance greater than ball radius
        if (dist > radSqr) { return } // no hit

        // solves the triangle from center to closest point on balls trajectory
        var d = Math.sqrt(radSqr - dist) / ballSpeed;

        // intercept point is closest to line start
        cpx -= s.dx * d;
        cpy -= s.dy * d;

        // get the distance from the ball current pos to the intercept point
        d = Math.hypot(cpx - s.x, cpy - s.y);

        // is the distance greater than the ball speed then its a miss
        if (d > ballSpeed) { return } // no hit return

        s.x = cpx; // position of contact
        s.y = cpy;

        // find the normalised tangent at intercept point 
        const ty = (cpx - batW2) / ball.r;
        const tx = -(cpy - batH2) / ball.r;

        // calculate the reflection vector
        const bsx = s.dx / ballSpeed; // normalise ball speed
        const bsy = s.dy / ballSpeed;
        const dot = (bsx * tx + bsy * ty) * 2;

        // get the distance the ball travels past the intercept
        d = ballSpeed - d;

        // the reflected vector is the balls new delta (this delta is normalised)
        s.dx = (tx * dot - bsx);
        s.dy = (ty * dot - bsy);

        // move the ball the remaining distance away from corner
        s.x += s.dx * d;
        s.y += s.dy * d;

        // set the ball delta to the balls speed
        s.dx *= ballSpeed
        s.dy *= ballSpeed
        hit = true;
    }

    // if the ball hit the bat restore absolute position
    if (hit) {
        // reverse mirror
        s.x *= mirrorX;
        s.dx *= mirrorX;
        s.y *= mirrorY;
        s.dy *= mirrorY;

        // remove bat relative position
        s.x += bat.x;
        s.y += bat.y;

        // remove bat relative delta
        s.dx += bat.dx;
        s.dy += bat.dy;

        // set the balls new position and delta
        ball.x = s.x;
        ball.y = s.y;
        ball.dx = s.dx;
        ball.dy = s.dy;
    }
}

function rotate(velocity, angle) {
    const rotatedVelocities = {
        x: velocity.x * Math.cos(angle) + velocity.y * Math.sin(angle),
        y: -velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle)
    };

    return rotatedVelocities;
}

function resolveCollision(particle, otherParticle) {
    const xVelocityDiff = particle.dx - otherParticle.dx;
    const yVelocityDiff = particle.dy - otherParticle.dy;

    const xDist = otherParticle.x - particle.x;
    const yDist = otherParticle.y - particle.y;

    if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) {

        const angle = Math.atan2(otherParticle.y - particle.y, otherParticle.x - particle.x);
        const m1 = particle.mass;
        const m2 = otherParticle.mass;

        const u1 = rotate({ x: particle.dx, y: particle.dy }, angle);
        const u2 = rotate({ x: otherParticle.dx, y: otherParticle.dy }, angle);

        const v1 = {
            x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2),
            y: u1.y
        };
        const v2 = {
            x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2),

            y: u2.y
        };

        const vFinal1 = rotate(v1, -angle);
        const vFinal2 = rotate(v2, -angle);

        particle.dx = vFinal1.x;
        particle.dy = vFinal1.y;

        otherParticle.dx = vFinal2.x;
        otherParticle.dy = vFinal2.y;
    }
}

for (let i = 0; i < 10; i++) {
    balls.push(new ball())
}
balls.push(new player())
    //x,y,w,h
bats.push(new bat((window.innerWidth / 2) - 150, window.innerHeight / 2, 10, 100))
bats.push(new bat((window.innerWidth / 2) + 150, window.innerHeight / 2, 10, 100))

// main update function
function update(timer) {

    if (w !== innerWidth || h !== innerHeight) {
        cw = (w = canvas.width = innerWidth) / 2;
        ch = (h = canvas.height = innerHeight) / 2;
    }



    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    ctx.globalAlpha = 1; // reset alpha
    ctx.clearRect(0, 0, w, h);

    // move bat and ball
    for (var i = 0; i < balls.length; i++) {
        for (var j = 0; j < bats.length; j++) {
            doBatBall(bats[j], balls[i])
        }
        balls[i].update()
        balls[i].draw(ctx)
    }
    bats.forEach(bat => {
        //bat.update();
        bat.draw(ctx);
    })

    // check for bal bat contact and change ball position and trajectory if needed

    // draw ball and bat

    requestAnimationFrame(update);

}
requestAnimationFrame(update);
<body>
    <canvas></canvas>
</body>

Upvotes: 1

Related Questions