admin管理员组

文章数量:1302555

I'm making a 2D game in JavaScript. For it, I need to be able to "perfectly" check collision between two sprites which have x/y positions (corresponding to their centre), a rotation in radians, and of course known width/height.

After spending many weeks of work (yeah, I'm not even exaggerating), I finally came up with a working solution, which unfortunately turned out to be about 10,000x too slow and impossible to optimize in any meaningful manner. I have entirely abandoned the idea of actually drawing and reading pixels from a canvas. That's just not going to cut it, but please don't make me explain in detail why. This needs to be done with math and an "imaginated" 2D world/grid, and from talking to numerous people, the basic idea became obvious. However, the practical implementation is not. Here's what I do and want to do:

What I already have done

In the beginning of the program, each sprite is pixel-looked through in its default upright position and a 1-dimensional array is filled up with data corresponding to the alpha channel of the image: solid pixels get represented by a 1, and transparent ones by 0. See figure 3.

The idea behind that is that those 1s and 0s no longer represent "pixels", but "little math orbs positioned in perfect distances to each other", which can be rotated without "losing" or "adding" data, as happens with pixels if you rotate images in anything but 90 degrees at a time.

I naturally do the quick "bounding box" check first to see if I should bother calculating accurately. This is done. The problem is the fine/"for-sure" check...

What I cannot figure out

Now that I need to figure out whether the sprites collide for sure, I need to construct a math expression of some sort using "linear algebra" (which I do not know) to determine if these "rectangles of data points", positioned and rotated correctly, both have a "1" in an overlapping position.

Although the theory is very simple, the practical code needed to acplish this is simply beyond my capabilities. I've stared at the code for many hours, asking numerous people (and had massive problems explaining my problem clearly) and really put in an effort. Now I finally want to give up. I would very, very much appreciate getting this done with. I can't even give up and "cheat" by using a library, because nothing I find even es close to solving this problem from what I can tell. They are all impossible for me to understand, and seem to have entirely different assumptions/requirements in mind. Whatever I'm doing always seems to be some special case. It's annoying.

This is the pseudo code for the relevant part of the program:

function doThisAtTheStartOfTheProgram()
{
    makeQuickVectorFromImageAlpha(sprite1);
    makeQuickVectorFromImageAlpha(sprite2);
}

function detectCollision(sprite1, sprite2)
{
    // This easy, outer check works. Please ignore it as it is unrelated to the problem.
    if (bounding_box_match)
    {
        /*

            This part is the entire problem.
            I must do a math-based check to see if they really collide.

            These are the relevant variables as I have named them:

                sprite1.x
                sprite1.y
                sprite1.rotation // in radians
                sprite1.width
                sprite1.height
                sprite1.diagonal // might not be needed, but is provided

                sprite2.x
                sprite2.y
                sprite2.rotation // in radians
                sprite2.width
                sprite2.height
                sprite2.diagonal // might not be needed, but is provided

                sprite1.vectorForCollisionDetection
                sprite2.vectorForCollisionDetection

            Can you please help me construct the math expression, or the series of math expressions, needed to do this check?
            To clarify, using the variables above, I need to check if the two sprites (which can rotate around their centre, have any position and any dimensions) are colliding. A collision happens when at least one "unit" (an imagined sphere) of BOTH sprites are on the same unit in our imaginated 2D world (starting from 0,0 in the top-left).

        */

        if (accurate_check_goes_here)
            return true;
    }

    return false;
}

In other words, "accurate_check_goes_here" is what I wonder what it should be. It doesn't need to be a single expression, of course, and I would very much prefer seeing it done in "steps" (with ments!) so that I have a chance of understanding it, but please don't see this as "spoon feeding". I fully admit I suck at math and this is beyond my capabilities. It's just a fact. I want to move on and work on the stuff I can actually solve on my own.

To clarify: the 1D arrays are 1D and not 2D due to performance. As it turns out, speed matters very much in JS World. Although this is a non-profit project, entirely made for private satisfaction, I just don't have the time and energy to order and sit down with some math book and learn about that from the ground up. I take no pride in lacking the math skills which would help me a lot, but at this point, I need to get this game done or I'll go crazy. This particular problem has prevented me from getting any other work done for far too long.

I hope I have explained the problem well. However, one of the most frustrating feelings is when people send well-meaning replies that unfortunately show that the person helping has not read the question. I'm not pre-insulting you all -- I just wish that won't happen this time! Sorry if my description is poor. I really tried my best to be perfectly clear.

Okay, so I need "reputation" to be able to post the illustrations I spent time to create to illustrate my problem. So instead I link to them:

Illustrations

  1. (censored by Stackoverflow)
  2. (censored by Stackoverflow)

OK. This site won't let me even link to the images. Only one. Then I'll pick the most important one, but it would've helped a lot if I could link to the others...

I'm making a 2D game in JavaScript. For it, I need to be able to "perfectly" check collision between two sprites which have x/y positions (corresponding to their centre), a rotation in radians, and of course known width/height.

After spending many weeks of work (yeah, I'm not even exaggerating), I finally came up with a working solution, which unfortunately turned out to be about 10,000x too slow and impossible to optimize in any meaningful manner. I have entirely abandoned the idea of actually drawing and reading pixels from a canvas. That's just not going to cut it, but please don't make me explain in detail why. This needs to be done with math and an "imaginated" 2D world/grid, and from talking to numerous people, the basic idea became obvious. However, the practical implementation is not. Here's what I do and want to do:

What I already have done

In the beginning of the program, each sprite is pixel-looked through in its default upright position and a 1-dimensional array is filled up with data corresponding to the alpha channel of the image: solid pixels get represented by a 1, and transparent ones by 0. See figure 3.

The idea behind that is that those 1s and 0s no longer represent "pixels", but "little math orbs positioned in perfect distances to each other", which can be rotated without "losing" or "adding" data, as happens with pixels if you rotate images in anything but 90 degrees at a time.

I naturally do the quick "bounding box" check first to see if I should bother calculating accurately. This is done. The problem is the fine/"for-sure" check...

What I cannot figure out

Now that I need to figure out whether the sprites collide for sure, I need to construct a math expression of some sort using "linear algebra" (which I do not know) to determine if these "rectangles of data points", positioned and rotated correctly, both have a "1" in an overlapping position.

Although the theory is very simple, the practical code needed to acplish this is simply beyond my capabilities. I've stared at the code for many hours, asking numerous people (and had massive problems explaining my problem clearly) and really put in an effort. Now I finally want to give up. I would very, very much appreciate getting this done with. I can't even give up and "cheat" by using a library, because nothing I find even es close to solving this problem from what I can tell. They are all impossible for me to understand, and seem to have entirely different assumptions/requirements in mind. Whatever I'm doing always seems to be some special case. It's annoying.

This is the pseudo code for the relevant part of the program:

function doThisAtTheStartOfTheProgram()
{
    makeQuickVectorFromImageAlpha(sprite1);
    makeQuickVectorFromImageAlpha(sprite2);
}

function detectCollision(sprite1, sprite2)
{
    // This easy, outer check works. Please ignore it as it is unrelated to the problem.
    if (bounding_box_match)
    {
        /*

            This part is the entire problem.
            I must do a math-based check to see if they really collide.

            These are the relevant variables as I have named them:

                sprite1.x
                sprite1.y
                sprite1.rotation // in radians
                sprite1.width
                sprite1.height
                sprite1.diagonal // might not be needed, but is provided

                sprite2.x
                sprite2.y
                sprite2.rotation // in radians
                sprite2.width
                sprite2.height
                sprite2.diagonal // might not be needed, but is provided

                sprite1.vectorForCollisionDetection
                sprite2.vectorForCollisionDetection

            Can you please help me construct the math expression, or the series of math expressions, needed to do this check?
            To clarify, using the variables above, I need to check if the two sprites (which can rotate around their centre, have any position and any dimensions) are colliding. A collision happens when at least one "unit" (an imagined sphere) of BOTH sprites are on the same unit in our imaginated 2D world (starting from 0,0 in the top-left).

        */

        if (accurate_check_goes_here)
            return true;
    }

    return false;
}

In other words, "accurate_check_goes_here" is what I wonder what it should be. It doesn't need to be a single expression, of course, and I would very much prefer seeing it done in "steps" (with ments!) so that I have a chance of understanding it, but please don't see this as "spoon feeding". I fully admit I suck at math and this is beyond my capabilities. It's just a fact. I want to move on and work on the stuff I can actually solve on my own.

To clarify: the 1D arrays are 1D and not 2D due to performance. As it turns out, speed matters very much in JS World. Although this is a non-profit project, entirely made for private satisfaction, I just don't have the time and energy to order and sit down with some math book and learn about that from the ground up. I take no pride in lacking the math skills which would help me a lot, but at this point, I need to get this game done or I'll go crazy. This particular problem has prevented me from getting any other work done for far too long.

I hope I have explained the problem well. However, one of the most frustrating feelings is when people send well-meaning replies that unfortunately show that the person helping has not read the question. I'm not pre-insulting you all -- I just wish that won't happen this time! Sorry if my description is poor. I really tried my best to be perfectly clear.

Okay, so I need "reputation" to be able to post the illustrations I spent time to create to illustrate my problem. So instead I link to them:

Illustrations

  1. (censored by Stackoverflow)
  2. (censored by Stackoverflow)

OK. This site won't let me even link to the images. Only one. Then I'll pick the most important one, but it would've helped a lot if I could link to the others...

Share Improve this question edited Feb 24, 2014 at 9:36 Frédéric Hamidi 263k42 gold badges495 silver badges484 bronze badges asked Jan 29, 2014 at 3:10 user3247047user3247047 1055 bronze badges 18
  • Are you rendering using <canvas> or vector? – Elliot Bonneville Commented Jan 29, 2014 at 3:17
  • 3 To rephrase - you have two pixel maps with known position and rotation and you want to know if distance of center of any non-zero pixel in one sprite to any non-zero pixel in the other sprite is less than the pixel to pixel distance. With a fast algorithm. Is that a fair summary? – Floris Commented Jan 29, 2014 at 3:17
  • What are the URLs of your other images? Let me know and I'll put them in for you. – Lee Taylor Commented Jan 29, 2014 at 3:20
  • Elliot: Your question doesn't make sense to me. I use Canvas 2D for drawing graphics, but this has nothing to do with the graphics/output. – user3247047 Commented Jan 29, 2014 at 3:20
  • 1 BTW the task has very little to do with linear algebra. The core of the problem is not rotation and translation, but an intersection problem. Therefore math is also not the silver bullet to solve this problem, but putational geometry, probably in the form of sweepline algorithm etc. It's by no means simple even if you understand the math. Now it might be simpler if you can do a quadratic collision detection, so if you can check every pair of sprites for a collision separately. It's still not easy though. – Niklas B. Commented Jan 29, 2014 at 4:10
 |  Show 13 more ments

4 Answers 4

Reset to default 4

First you need to understand that detecting such collisions cannot be done with a single/simple equation. Because the shapes of the sprites matter and these are described by an array of Width x Height = Area bits. So the worst-case plexity of the algorithm must be at least O(Area).

Here is how I would do it:

Represent the sprites in two ways:

1) a bitmap indicating where pixels are opaque,

2) a list of the coordinates of the opaque pixels. [Optional, for speedup, in case of hollow sprites.]

Choose the sprite with the shortest pixel list. Find the rigid transform (translation + rotation) that transforms the local coordinates of this sprite into the local coordinates of the other sprite (this is where linear algebra es into play - the rotation is the difference of the angles, the translation is the vector between upper-left corners - see http://planning.cs.uiuc.edu/node99.html).

Now scan the opaque pixel list, transforming the local coordinates of the pixels to the local coordinates of the other sprite. Check if you fall on an opaque pixel by looking up the bitmap representation.

This takes at worst O(Opaque Area) coordinate transforms + pixel tests, which is optimal.

If you sprites are zoomed-in (big pixels), as a first approximation you can ignore the zooming. If you need more accuracy, you can think of sampling a few points per pixel. Exact putation will involve a square/square collision intersection algorithm (with rotation), more plex and costly. See http://en.wikipedia/wiki/Sutherland%E2%80%93Hodgman_algorithm.

Here is an exact solution that will work regardless the size of the pixels (zoomed or not).

Use both a bitmap representation (1 opacity bit per pixel) and a deposition into squares or rectangles (rectangles are optional, just an optimization; single pixels are ok).

Process all rectangles of the (source) sprite in turn. By means of rotation/translation, map the rectangles to the coordinate space of the other sprite (target). You will obtain a rotated rectangle overlaid on a grid of pixels.

Now you will perform a filling of this rectangle with a scanline algorithm: first split the rectangle in three (two triangles and one parallelogram), using horizontal lines through the rectangle vertexes. For the three shapes independently, find all horizontal between-pixel lines that cross them (this is simply done by looking at the ranges of Y values). For every such horizontal line, pute the two intersections points. Then find all pixel corners that fall between the two intersections (range of X values). For any pixel having a corner inside the rectangle, lookup the corresponding bit in the (target) sprite bitmap.

No too difficult to program, no plicated data structure. The putational effort is roughly proportional to the number of target pixels covered by every source rectangle.

Although you have already stated that you don't feel rendering to the canvas and checking that data is a viable solution, I'd like to present an idea which may or may not have already occurred to you and which ought to be reasonably efficient.

This solution relies on the fact that rendering any pixel to the canvas with half-opacity twice will result in a pixel of full opacity. The steps follow:

  • Size the test canvas so that both sprites will fit on it (this will also clear the canvas, so you don't have to create a new element each time you need to test for collision).
  • Transform the sprite data such that any pixel that has any opacity or color is set to be black at 50% opacity.
  • Render the sprites at the appropriate distance and relative position to one another.
  • Loop through the resulting canvas data. If any pixels have an opacity of 100%, then a collision has been detected. Return true.
  • Else, return false.
  • Wash, rinse, repeat.

This method should run reasonably fast. Now, for optimization--the bottleneck here will likely be the final opacity check (although rendering the images to the canvas could be slow, as might be clearing/resizing it):

  • reduce the resolution of the opacity detection in the final step, by changing the increment in your loop through the pixels of the final data.
  • Loop from middle up and down, rather than from the top to bottom (and return as soon as you find any single collision). This way you have a higher chance of encountering any collisions earlier in the loop, thus reducing its length.

I don't know what your limitations are and why you can't render to canvas, since you have declined to ment on that, but hopefully this method will be of some use to you. If it isn't, perhaps it might e in handy to future users.

Please see if the following idea works for you. Here I create a linear array of points corresponding to pixels set in each of the two sprites. I then rotate/translate these points, to give me two sets of coordinates for individual pixels. Finally, I check the pixels against each other to see if any pair are within a distance of 1 - which is "collision".

You can obviously add some segmentation of your sprite (only test "boundary pixels"), test for bounding boxes, and do other things to speed this up - but it's actually pretty fast (once you take all the console.log() statements out that are just there to confirm things are behaving…). Note that I test for dx - if that is too large, there is no need to pute the entire distance. Also, I don't need the square root for knowing whether the distance is less than 1.

I am not sure whether the use of new array() inside the pixLocs function will cause a problem with memory leaks. Something to look at if you run this function 30 times per second...

<html>
<script type="text/javascript">
var s1 = {
  'pix': new Array(0,0,1,1,0,0,1,0,0,1,1,0),
  'x': 1,
  'y': 2,
  'width': 4,
  'height': 3,
  'rotation': 45};

var s2 = {
  'pix': new Array(1,0,1,0,1,0,1,0,1,0,1,0),

  'x': 0,
  'y': 1,
  'width': 4,
  'height': 3,
  'rotation': 90};

pixLocs(s1);
console.log("now rotating the second sprite...");

pixLocs(s2);
console.log("collision detector says " + collision(s1, s2));

function pixLocs(s) {
  var i;
  var x, y;
  var l1, l2;
  var ca, sa;
  var pi;
  s.locx = new Array();
  s.locy = new Array();
  pi = Math.acos(0.0) * 2;
  var l = new Array();
  ca = Math.cos(s.rotation * pi / 180.0);
  sa = Math.sin(s.rotation * pi / 180.0);
  i = 0;
  for(x = 0; x < s.width; ++x) {
    for(y = 0; y < s.height; ++y) {
      // offset to center of sprite
      if(s.pix[i++]==1) {
        l1 = x - (s.width - 1) * 0.5;
        l2 = y - (s.height - 1) * 0.5;
        // rotate:
        r1 = ca * l1 - sa * l2;
        r2 = sa * l1 + ca * l2;
        // add position:
        p1 = r1 + s.x;
        p2 = r2 + s.y;
        console.log("rotated pixel [ " + x + "," +  y + " ] is at ( " +  p1 + "," + p2 + " ) " );
        s.locx.push(p1);
        s.locy.push(p2);
      }
      else console.log("no pixel at [" + x + "," + y + "]");
    }
  }
}

function collision(s1, s2) {
  var i, j;
  var dx, dy;
  for (i = 0; i < s1.locx.length; i++) {
    for (j = 0; j < s2.locx.length; j++) {
      dx = Math.abs(s1.locx[i] - s2.locx[j]);
      if(dx < 1) {
        dy = Math.abs(s1.locy[i] - s2.locy[j]);
        if (dx*dx + dy+dy < 1) return 1;
      }
    }
  }
  return 0;
}
</script>
</html>

本文标签: