圆形线段碰撞

4

我需要检测圆形与任何线段的碰撞。我有一个包含多边形顶点坐标(x, y)的数组,并在循环中绘制此多边形。为了检测碰撞,我使用算法计算三角形高度。然后我检查这个高度是否小于0,如果是,则圆形与该线段发生了碰撞。

下面是描述此方法的图片:

enter image description here

但是我得到了意想不到的结果。我的圆形与透明线碰撞了(什么?)。我无法解释它是如何发生的。 在 jsfiddle 上查看演示:https://jsfiddle.net/f458rdz6/1/ 检查碰撞并响应的函数:
var p = polygonPoints;

for (var i = 0, n = p.length; i < n; i++) {
    var start = i;
    var end = (i + 1) % n;

    var x0 = p[start].x;
    var y0 = p[start].y;
    var x1 = p[end].x;
    var y1 = p[end].y;

    // detection collision
    var dx = x1 - x0;
    var dy = y1 - y0;

    var len = Math.sqrt(dx * dx + dy * dy);
    var dist = (dx * (this.y - y0) - dy * (this.x - x0)) / len;

    if (dist < this.radius) {
        continue;
    }

    // calculate reflection, because collided
    var wallAngle = Math.atan2(dy, dx);
    var wallNormalX = Math.sin(wallAngle);
    var wallNormalY = -Math.cos(wallAngle);

    var d = 2 * (this.velocityX * wallNormalX + this.velocityY * wallNormalY);
    this.x -= d * wallNormalX;
    this.y -= d * wallNormalY;
}

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var polygonPoints = [
 {
     x: 240,
        y: 130
    },
    {
     x: 140,
        y: 100
    },
    {
     x: 180,
        y: 250
    },
    {
     x: 320,
        y: 280
    },
    {
     x: 400,
        y: 50
    }
];

var game = {
 ball: new Ball()
};

function Ball() {
 this.x = canvas.width / 2;
    this.y = canvas.height - 100;
    this.oldX = this.x - 1;
    this.oldY = this.y + 1;
    this.velocityX = 0;
    this.velocityY = 0;
    this.radius = 8;
};

Ball.prototype.draw = function() {
 ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fillStyle = '#0095DD';
    ctx.fill();
    ctx.closePath();
};

Ball.prototype.update = function() {
 var x = this.x;
    var y = this.y;
    
    this.velocityX = this.x - this.oldX;
    this.velocityY = this.y - this.oldY;
    
    this.x += this.velocityX;
    this.y += this.velocityY;
    
    this.oldX = x;
    this.oldY = y;
};

Ball.prototype.collision = function() {
 var p = polygonPoints;
    
 for (var i = 0, n = p.length; i < n; i++) {
     var start = i;
        var end = (i + 1) % n;
        
        var x0 = p[start].x;
        var y0 = p[start].y;
        var x1 = p[end].x;
        var y1 = p[end].y;
        
        // detection collision
        var dx = x1 - x0;
        var dy = y1 - y0;
        
        var len = Math.sqrt(dx * dx + dy * dy);
        var dist = (dx * (this.y - y0) - dy * (this.x - x0)) / len;
        
        if (dist < this.radius) {
         continue;
        }

  // calculate reflection, because collided
        var wallAngle = Math.atan2(dy, dx);
        var wallNormalX = Math.sin(wallAngle);
        var wallNormalY = -Math.cos(wallAngle);

        var d = 2 * (this.velocityX * wallNormalX + this.velocityY * wallNormalY);
        this.x -= d * wallNormalX;
        this.y -= d * wallNormalY;
    }
};


function drawBall() {
    ctx.beginPath();
    ctx.arc(x, y, ballRadius, 0, Math.PI*2);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
}

function drawPolygon() {
    ctx.beginPath();
    ctx.strokeStyle = '#333';
    ctx.moveTo(polygonPoints[0].x, polygonPoints[0].y);
 for (var i = 1, n = polygonPoints.length; i < n; i++) {
     ctx.lineTo(polygonPoints[i].x, polygonPoints[i].y);
    }
    ctx.lineTo(polygonPoints[0].x, polygonPoints[0].y);
    ctx.stroke();
    ctx.closePath();
}

function render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawPolygon();
    game.ball.draw();
    game.ball.update();
    game.ball.collision();
    window.requestAnimationFrame(render);
}

render();
canvas {
    border: 1px solid #333;
}
<canvas id="myCanvas" width="480" height="320"></canvas>

什么问题?也许我需要使用其他方法来检测碰撞?我尝试使用这个, 但如果我的圆速度很快,这个方法不起作用。
谢谢。
1个回答

21

圆线段截点

更新

本答案包括线线截距、沿着其法线移动线、点(圆)到线的距离以及圆线段截点。

该圆为:

var circle = {
    radius : 500,
    center : point(1000,1000),
}

这条线段是

var line = {
    p1 : point(500,500),
    p2 : point(2000,1000),
}

一个点是

var point = {
    x : 100,
    y : 100,
}

寻找直线段与圆相交点的函数。

该函数返回一个包含最多两个直线段上的点的数组。如果没有找到任何点,则返回一个空数组。

function inteceptCircleLineSeg(circle, line){
    var a, b, c, d, u1, u2, ret, retP1, retP2, v1, v2;
    v1 = {};
    v2 = {};
    v1.x = line.p2.x - line.p1.x;
    v1.y = line.p2.y - line.p1.y;
    v2.x = line.p1.x - circle.center.x;
    v2.y = line.p1.y - circle.center.y;
    b = (v1.x * v2.x + v1.y * v2.y);
    c = 2 * (v1.x * v1.x + v1.y * v1.y);
    b *= -2;
    d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - circle.radius * circle.radius));
    if(isNaN(d)){ // no intercept
        return [];
    }
    u1 = (b - d) / c;  // these represent the unit distance of point one and two on the line
    u2 = (b + d) / c;    
    retP1 = {};   // return points
    retP2 = {}  
    ret = []; // return array
    if(u1 <= 1 && u1 >= 0){  // add point if on the line segment
        retP1.x = line.p1.x + v1.x * u1;
        retP1.y = line.p1.y + v1.y * u1;
        ret[0] = retP1;
    }
    if(u2 <= 1 && u2 >= 0){  // second add point if on the line segment
        retP2.x = line.p1.x + v1.x * u2;
        retP2.y = line.p1.y + v1.y * u2;
        ret[ret.length] = retP2;
    }       
    return ret;
}

更新

直线相交点。

如果找到返回一个点,否则返回undefined。

function interceptLines(line,line1){  
    var v1, v2, c, u;
    v1 = {};
    v2 = {};
    v3 = {};
    v1.x = line.p2.x - line.p1.x; // vector of line
    v1.y = line.p2.y - line.p1.y;
    v2.x = line1.p2.x - line1.p1.x; //vector of line2
    v2.y = line1.p2.y - line1.p1.y;
    var c = v1.x * v2.y - v1.y * v2.x; // cross of the two vectors
    if(c !== 0){ 
        v3.x = line.p1.x - line1.p1.x; 
        v3.y = line.p1.y - line1.p1.y;                
        u = (v2.x * v3.y - v2.y * v3.x) / c; // unit distance of intercept point on this line
        return {x : line.p1.x + v1.x * u, y : line.p1.y + v1.y * u};
    }
    return undefined;
}

提升线

沿其法线移动线。

function liftLine(line,dist){
    var v1,l
    v1 = {};
    v1.x = line.p2.x - line.p1.x; // convert line to vector
    v1.y = line.p2.y - line.p1.y;
    l = Math.sqrt(v1.x * v1.x + v1.y * v1.y); // get length;
    v1.x /= l;  // Assuming you never pass zero length lines
    v1.y /= l;
    v1.x *= dist; // set the length
    v1.y *= dist;
    // move the line along its normal the required distance
    line.p1.x -= v1.y;
    line.p1.y += v1.x;
    line.p2.x -= v1.y;
    line.p2.y += v1.x;

    return line; // if needed 
}

距离线段的圆(或点)

返回到线段最近的距离。这只是我正在使用的圆心。因此,您可以将圆替换为点。

function circleDistFromLineSeg(circle,line){
    var v1, v2, v3, u;
    v1 = {};
    v2 = {};
    v3 = {};
    v1.x = line.p2.x - line.p1.x;
    v1.y = line.p2.y - line.p1.y;
    v2.x = circle.center.x - line.p1.x;
    v2.y = circle.center.y - line.p1.y;
    u = (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x); // unit dist of point on line
    if(u >= 0 && u <= 1){
        v3.x = (v1.x * u + line.p1.x) - circle.center.x;
        v3.y = (v1.y * u + line.p1.y) - circle.center.y;
        v3.x *= v3.x;
        v3.y *= v3.y;
        return Math.sqrt(v3.y + v3.x); // return distance from line
    } 
    // get distance from end points
    v3.x = circle.center.x - line.p2.x;
    v3.y = circle.center.y - line.p2.y;
    v3.x *= v3.x;  // square vectors
    v3.y *= v3.y;    
    v2.x *= v2.x;
    v2.y *= v2.y;
    return Math.min(Math.sqrt(v2.y + v2.x), Math.sqrt(v3.y + v3.x)); // return smaller of two distances as the result
}

这个方法适用于球速较慢的情况,但如果速度很快,函数就无法正常工作。请查看https://jsfiddle.net/cmx8arz2/中的控制台。如果圆与线相交,应该打印出相交点的数组,但是由���圆的速度过快,控制台日志被清空了。 - rmpstmp
@rmpstmp 看一下这个代码片段。将所有线段向内移动球的半径(沿着线垂线)。然后使用球的轨迹作为一条线,并进行线/线拦截。找到距离球当前位置最近的拦截点,您就得到了拦截点,不需要使用圆形线段拦截。 - Blindman67
好的,你能描述一下,我应该向函数interceptLines(line,line1)发送哪些行吗?其中一条线应该是墙壁线,但第二条呢?我认为可以使用球的中心线(b.x, b.y)作为一条线,那第二个点应该是什么呢?谢谢。 - rmpstmp
@rmpstmp 你需要将墙壁线向球的半径方向移动。你应该添加一些简单的函数来绘制点和线以进行调试,以便你可以可视化各种测试的结果。你需要执行我在答案中添加的线段距离测试。这将给出碰撞点。测试当前球距离这些截距的距离,只保留最小距离。然后,如果该距离小于球速度,则表示撞到了墙。对于嵌入式墙壁的拦截,如果拦截距离小于半径,请丢弃该点,然后找到最接近的点,如果该点距离小于球速度,则表示撞到了墙。 - Blindman67
1
你在这里编写了一些不错的功能 - 点赞!:-) 使用你的函数,可以创建一个线段,表示球从起始位置到结束位置的前沿。然后计算线-线截距(前沿线与障碍线),以获取球与障碍物的初始碰撞点。之后,计算一些反弹角度,生活就美好了。 - markE
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接