سؤال

If given a n-sided polygon, and a line of length k (at x,y and angle a), is there an algorithm to detect which side of the polygon (if any) I have collided with? So far I've resorted to testing if x,y is outside of the polygon, and then iterating through each edge of the polygon, calculating the distance to each end. Here is a JS Fiddle that shows the world I've created.

Here is the JavaScript (the HTML and CSS aren't really worth copying):

var eventLoop,
    maxVelocity = 10,
    agility = 5,
    baseLength = 5,
    degree = ((2*Math.PI)/360),
    world = document.getElementById('world'),
    context = world.getContext("2d"),
    boundry = [[180, 120],[240, 60],[360, 40],[420, 120],[360, 220],[350, 240],[360, 265],[470,360],[450,480],[360,540],[240,550],[140,480],[120,470],[100,360],[120,300],[220,240],[240,220]],
    camera = {
        location: {
            x:300,
            y:90
        },
        angle: 0,
        velocity: 0
    },
    engine = {
        drawWorld: function(shape, context) {
            var point,
                index,
                size = shape.length;

            context.clearRect(0, 0, world.width, world.height);
            context.beginPath();
            for(index = 0; index < size; index++) {
                point = shape[index];
                if(index == 0) {
                    context.moveTo(point[0], point[1]);
                } else {
                    context.lineTo(point[0], point[1]);
                }
            }
            context.closePath();
            context.stroke();
        },
        drawCamera: function(camera, context) {
            var a = camera.location,
                b = this.calcNextPoint(camera, 1);

            context.beginPath();
            context.moveTo(a.x, a.y);
            context.lineTo(b.x, b.y);
            context.stroke();

            context.beginPath();
            context.arc(a.x, a.y, baseLength, 0, Math.PI*2, true); 
            context.closePath();
            context.stroke();
        },
        calcNextPoint: function(camera, moment) {
            return {
                x: camera.location.x + ((camera.velocity*(1/moment))*Math.sin(camera.angle)),
                y: camera.location.y + ((camera.velocity*(1/moment))*(Math.cos(camera.angle)))
            };
        },
        isInside: function(point, shape) {
            var i, j, c = 0;
            for (i = 0, j = shape.length - 1; i < shape.length; j = i++) {
                if (((shape[i][1] > point.y) != (shape[j][1] > point.y)) && (point.x < (shape[j][0] - shape[i][0]) * (point.y - shape[i][1]) / (shape[j][1] - shape[i][1]) + shape[i][0])) {
                     c = !c;
                }
            }
            return c;
        }
    };

document.onkeydown = function(e) {
    e = e || window.event;
    if (e.keyCode == '37') {
        // left arrow
        camera.angle += degree*agility;
    }
    else if (e.keyCode == '39') {
        // right arrow
        camera.angle -= degree*agility;
    }
    else if (e.keyCode == '38') {
        // up arrow
        camera.velocity += 1;
        if(camera.velocity > maxVelocity) {
            camera.velocity = maxVelocity;
        }
    }
    else if (e.keyCode == '40') {
        // down arrow
        camera.velocity -= 1;
        if(camera.velocity < 0) {
            camera.velocity = 0;
        }
    }
}

engine.drawWorld(boundry, context);
engine.drawCamera(camera, context);

eventLoop = setInterval(function() {
    engine.drawWorld(boundry, context);
    engine.drawCamera(camera, context);
    if(engine.isInside(camera.location, boundry)) {
        camera.location = engine.calcNextPoint(camera, 1);
    }
}, 100);

I've been toying around with some JavaScript that models a 2-Dementional version of the game Flower by ThatGameComapny, eventually I want to try and implement a Oculus Rift version. The next problem I'm looking to tackle is a routine to turn the player back into the polygon once they have collided with an edge.

هل كانت مفيدة؟

المحلول

So you basicly want to know which edges of the polygon are intersected by given line, right?

This is just quite simple handling of a few linear equations representing the edges (inequations, to be more precise). You already have a great implementation of the inside operation, which also does that. The common denominator of all these algorithms is to compare at which side of the line [x1, y1] - [x2, y2] the point [x, y] lies:

compare = function (a, b) { // classical compare function, returns -1, 0, 1
    return a < b ? -1 : (a == b ? 0 : 1);
}
...
_lineSide: function (x, y, x1, y1, x2, y2) {
    return compare(x - x1, (x2 - x1) * (y - y1) / (y2 - y1));
}

This function will return -1 if [x, y] lies on one side of the line [x1, y1] - [x2, y2], 1 for the other side and 0 if it lies exactly on the line. It is not important here which side is which one, just to separate them. This will however not work when y2 - y1 is zero or close to zero. In that case you have to x-y flip the situation:

lineSide: function (x, y, x1, y1, x2, y2) {
    var eps = 1e-20; // some very small number
    if (Math.abs(y2 - y1) > eps)       // this way we avoid division by small number
        return _lineSide(x, y, x1, y1, x2, y2);
    else if (Math.abs(x2 - x1) > eps)  // flip the situation for horizontal lines
        return _lineSide(y, x, y1, x1, y2, x2);
    else // edge has close-to-zero length!
        throw new this.zeroLengthLineException()
},
zeroLengthLineException: function () {},

Now to test if the two lines [x1, y1] - [x2, y2] and [x3, y3] - [x4, y4] intersect is very easy. Just look if [x1, y1] and [x2, y2] lie on the opposite side of [x3, y3] - [x4, y4], and if [x3, y3] and [x4, y4] lie on the opposite side of [x1, y1] - [x2, y2]. If yes, then the lines intersect!

// proper: true/false (default false) - if we want proper intersection, i.e. just 
// touching doesn't count
linesIntersect: function (x1, y1, x2, y2, x3, y3, x4, y4, proper) {
    var min_diff = proper ? 2 : 1;
    return Math.abs(this.lineSide(x1, y1, x3, y3, x4, y4) - 
                    this.lineSide(x2, y2, x3, y3, x4, y4)) >= min_diff 
        && Math.abs(this.lineSide(x3, y3, x1, y1, x2, y2) - 
                    this.lineSide(x4, y4, x1, y1, x2, y2)) >= min_diff;
},

Final solution - http://jsfiddle.net/gLTpT/7/

Now, the final solution is easy: just check the intersection of given line with all of the polygon edges by calling linesIntersect in loop:

http://jsfiddle.net/gLTpT/7/

enter image description here

نصائح أخرى

A generic LS to LS test would consists of finding if the polygon end points P[i], P[i+1] lie in the different side of the vector (p, p+d), where d is the direction vector. In order of the lines to cross, also the opposite must hold: both (p, p+d) must be on different side of the line segment P[i], P[i+1].

  o  P[i]              Here P[i], P[i+1] are on different side of p -> q=p+d
   \                   but not the opposite.
    \     q__----p
     \
      o  P[i+1]

In complex or concave scenarios the vector can cross several segments and one needs to solve the closest segment. This is easiest done from the parametric equation

  (x0, y0) + (t * dx, t * dy) == i[0] + u * (i[1]-i[0]), j[0] + u * (j[1]-j[0])

EDIT Comparing the signed distances of point (i[n],j[n]) to a line (x0,y0) -> (x0 + dx, x1 +dy) requires two dot products and comparison of the sign.

  sameSide = sign((i0-x0)*(-dy)+(j0-y0)*dx) == sign((i1-x0)*(-dy)+(j1-y0)*dx))

In javascript this may be most efficiently done by a * b > 0, where a and b are the expressions whose signs are under comparison.

Here is a little library that will do what you need. I also incorporated the lib into a rewrite of your program. It's in this Gist, which you can download and open in your browser. (I had been meaning to try HTML5 canvas graphics forever. Thanks for giving me this inspiration!)

Also got running here in JS Fiddle.

The library uses a bit of linear algebra to find the intersection of two line segments, if it exists, or tell you if it doesn't. To determine where you've collided, just check each edge of the boundary against the line segment from the camera position to the position you're about to occupy. If there's an intersect, you have a collision.

If your camera is taking big steps, or if you are trying to determine where a bullet will strike before it's fired, then you do need to worry about the possibility of crossing multiple edges in one step and getting the right one of several possible intersections. However this little library makes it easy to do that. Say your step is a-->b. Then keep track of the min intersecting parameter t_ab. This one gives you both the correct colliding edge and collision point.

I did this with the light gray line segment in the animation.

My code just checks for any intersection and, if found, prevents the step from being taken, so the camera stops one partial step short of moving outside the boundary.

A more interesting and fun thing to try would be to move the camera to the exact intersection point and then compute the physically correct "bounce" direction so it continues to move, pinging off the walls. If you want to know how that works, ask and I can show you.

There are fancier (faster) ways to do collision detection, but this simple method works fine for the number of segments in this little program. See the classic "Collision Detection in Interactive 3D Environments" by Gino Van Den Bergen for a nice treatment of this subject. His discussion is obviously for 3D, but the ideas work well in 2D and many of his examples are in 2D as well.

var lib = {
  // Find parameters of intersection of segments a->b and c->d or null for parallel lines
  intersectionParams: function(a, b, c, d) {
    var dx_ab = b.x - a.x
    var dy_ab = b.y - a.y
    var dx_dc = c.x - d.x
    var dy_dc = c.y - d.y
    var eps = 0.00001
    var det = dx_ab * dy_dc - dx_dc * dy_ab
    if (-eps < det && det < eps) return null
    var dx_ac = c.x - a.x
    var dy_ac = c.y - a.y
    var t_ab = (dx_ac * dy_dc - dx_dc * dy_ac) / det
    var t_cd = (dx_ab * dy_ac - dx_ac * dy_ab) / det
    return { t_ab: t_ab, t_cd: t_cd }
  },

  // Determine if intersection parameters represent an intersection.
  // This always counts parallel lines as non-intersecting even if they're touching.
  areIntersecting: function(ip) {
    return ip != null && 0 <= ip.t_ab && ip.t_ab <= 1 && 0 <= ip.t_cd && ip.t_cd <= 1
  },

  // Find the intersection from its parameters and two points.
  intersection: function(ip, a, b) {
    return { x: a.x + ip.t_ab * (b.x - a.x), y: a.y + ip.t_ab * (b.y - a.y) }
  }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top