• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

GameMaker How to check if A contains B(B is totally inside A) with collision mask

D

djnjbjb

Guest
It is convenient to check collision in GMS2. Such as place_meeting() or collision event can be used.
Now I want check if A contains B with collision mask.
I did some search and found little about this.

Sometimes I may use collision checking to simulate containing checking.
But if the collision mask of A is not rectangle, it may be impossible to do this.


There are differences between containing and no collision.
Is there any way to check containing precisely with collision mask of GMS2?

(The images are small, you can click on them to view big ones.
I do not know how to make these images bigger, anyone can help me?)
 

Attachments

Last edited by a moderator:
D

djnjbjb

Guest
See rectangle_in_circle().
The rest is raw logic.
Thank you.
The image is a circle and a rectangle. But what I want to know is for each type of collision mask GMS2 offers, how to judge if A contains B, not only about rectangle and circle.
 

TailBit

Member
If there is a collision you could switch to a sprite with just an outside edge, then check again before changing back.

Or use the object sprite to punch hole in a surface, then turn it to a sprite and check for collision.

I don't know if GMS2 got some better options for this type of sprite detection.
 
There are three ways to check for collisions - not just for circles. So there's that, but as Simon Gust said: you can use raw logic.

If you're checking a rectangle: get the position of its sides and store them as variables.

Your other object is a triangle:
is point 1 larger than left side / top side? is it less than right side / bottom side?
is point 2 larger than left side / top side? is it less than right side / bottom side?
is point 3 larger than left side / top side? is it less than right side / bottom side?

OR

point 1: is point in rectangle?
point 2: is point in rectangle?
point 3: is point in rectangle?

If all true - is fully contained by the rectangular area

other object is a circle:
define up / left / right / bottom of the circle [x / y + lengthdir_x / y (radius, 0 / 90 / 180 / 270)]
repeat the above kind of comparisons for each point (basically you might as well treat it as a square)

rectangle in triangle:
do point_in_triangle for each corner of the rectangle
if any are false - is not totally consumed by triangles area

and so on

However: GMS does actually return several values from its own collision checks. 0 = no collision / 1 = fully encompassed / 2 = partial collision

As long as the smaller object is the first object, and the larger one is used for the second.

i.e
rectangle is smallest, triangle is largest = rectangle_in_triangle
triangle is smallest, rectangle is largest = triangle_in_rectangle

The "in" part of the function is significant. If you did triangle_in_rectangle, and the triangle totally consumes the rectangle, it will show as zero. Because, although there is a collision, it isn't within the parameters of how the function checks results. The triangle is not "in" the rectangle, its the other way around. Reverse the object order (rectangle_in_triangle) and it would show 1, because the rectangle is "in" the triangle. It's quite literal with its definition of the function.
 
D

djnjbjb

Guest
There are three ways to check for collisions - not just for circles. So there's that, but as Simon Gust said: you can use raw logic.

If you're checking a rectangle: get the position of its sides and store them as variables.

Your other object is a triangle:
is point 1 larger than left side / top side? is it less than right side / bottom side?
is point 2 larger than left side / top side? is it less than right side / bottom side?
is point 3 larger than left side / top side? is it less than right side / bottom side?

OR

point 1: is point in rectangle?
point 2: is point in rectangle?
point 3: is point in rectangle?

If all true - is fully contained by the rectangular area

other object is a circle:
define up / left / right / bottom of the circle [x / y + lengthdir_x / y (radius, 0 / 90 / 180 / 270)]
repeat the above kind of comparisons for each point (basically you might as well treat it as a square)

rectangle in triangle:
do point_in_triangle for each corner of the rectangle
if any are false - is not totally consumed by triangles area

and so on

However: GMS does actually return several values from its own collision checks. 0 = no collision / 1 = fully encompassed / 2 = partial collision

As long as the smaller object is the first object, and the larger one is used for the second.

i.e
rectangle is smallest, triangle is largest = rectangle_in_triangle
triangle is smallest, rectangle is largest = triangle_in_rectangle

The "in" part of the function is significant. If you did triangle_in_rectangle, and the triangle totally consumes the rectangle, it will show as zero. Because, although there is a collision, it isn't within the parameters of how the function checks results. The triangle is not "in" the rectangle, its the other way around. Reverse the object order (rectangle_in_triangle) and it would show 1, because the rectangle is "in" the triangle. It's quite literal with its definition of the function.
My initial thought was to find a solution for all kinds of collision mask in GMS2. Such as rectangle, ellipse, diamond, precise. I thought there may be a simple solution for all these.
After reading replies from you and Simon Gust. I think there is no simple solution for these.
So I may follow your suggestion.I may do the checking with regard to shapes.
In my project, most of the time they are rectangles, which are very easy to check.
 
D

djnjbjb

Guest
If there is a collision you could switch to a sprite with just an outside edge, then check again before changing back.

Or use the object sprite to punch hole in a surface, then turn it to a sprite and check for collision.

I don't know if GMS2 got some better options for this type of sprite detection.
The ideas of checking outside edge and punching hole are inspiring.
 
A diamond shape is just two triangles. By combining different results you check to see if its contained.

That shape isn't supported directly, but triangle_in_rectangle performed twice (to replicate the diamond shape) gets you whether there is a collision to begin with. If both checks return 1, then the diamond is fully contained. If one result is different, then you know it isn't.

With an ellipse that gets trickier, and I must admit I haven't a clue how you would approach it.

You could treat it as a more manageable shape, but only if it has a straight horizontal, or vertical, alignment. Then it can basically be treated as a rectangle, and is easily done (er...kind of..because of this next problem) Having said that...uh....how do you work out whether an ellipsis even has a horizontal / vertical alignment?

If its alignment is rotated then you maybe have to get a bit more creative: treat it as a trapezoid (rhombus?) shape, and split that into two triangles? But I can imagine scenarios where that wouldn't work, and how you'd figure out the points of the trapezoid / rhombus is also something I wouldn't know.
example.png
In this image the two purple arrows are showing where this suggestion would return an incorrect value for whether its "contained", as two of the points are outside the rectangle. But the (badly drawn) ellipsis is actually "contained"

So: (1) it's inaccurate, and (2) I have no idea how you'd even figure out the points for the triangles corners.....
 
D

djnjbjb

Guest
A diamond shape is just two triangles. By combining different results you check to see if its contained.

That shape isn't supported directly, but triangle_in_rectangle performed twice (to replicate the diamond shape) gets you whether there is a collision to begin with. If both checks return 1, then the diamond is fully contained. If one result is different, then you know it isn't.

With an ellipse that gets trickier, and I must admit I haven't a clue how you would approach it.

You could treat it as a more manageable shape, but only if it has a straight horizontal, or vertical, alignment. Then it can basically be treated as a rectangle, and is easily done (er...kind of..because of this next problem) Having said that...uh....how do you work out whether an ellipsis even has a horizontal / vertical alignment?

If its alignment is rotated then you maybe have to get a bit more creative: treat it as a trapezoid (rhombus?) shape, and split that into two triangles? But I can imagine scenarios where that wouldn't work, and how you'd figure out the points of the trapezoid / rhombus is also something I wouldn't know.
View attachment 27622
In this image the two purple arrows are showing where this suggestion would return an incorrect value for whether its "contained", as two of the points are outside the rectangle. But the (badly drawn) ellipsis is actually "contained"

So: (1) it's inaccurate, and (2) I have no idea how you'd even figure out the points for the triangles corners.....
Thank you. That's more than enough for me.
Most of the time both of them are rectangles, which is easy.
The idea of regarding diamond as two triangles is inspiring. The idea of using other shapes to simulate a shape hard to check is also inspiring.
So, I think it depends on how precise I need the check to be. There will always be a way.

I come up with an idea of using tangent line to simulate ellipse.
First tangent line, then split the shape to triangles.
I'm not sure if this idea is easy to accomplish. Just an idea I want to share with you.
tangent line.png
 

Neptune

Member
Assuming youre working with an object, loop across it (probably not very efficient if called 60x/s...)
Set your loops constraints based on sprite size, and utilize "precise" collision mask in sprite editor, and you got any shape. Optimize it depending on how accurate you need (maybe you don't need to check every pixel?).
Code:
var contained = true;
for(var a = 0; a < 100; a++)
{
   for(var i = 0; i < 100; i++)
   {
      var xx = x+i;
      var yy = y+a;
      if position_meeting(xx,yy,id) && !point_in_rectangle(x1,y1,x2,y2) {contained = false;}
   }
}
I also recommend making it a script!

Oh, and if the containment area itself isnt a simple shape, use another object and a second 'position_meeting' check...
 
Last edited:
D

djnjbjb

Guest
Assuming youre working with an object, loop across it (probably not very efficient if called 60x/s...)
Set your loops constraints based on sprite size, and utilize "precise" collision mask in sprite editor, and you got any shape. Optimize it depending on how accurate you need (maybe you don't need to check every pixel?).
Code:
var contained = true;
for(var a = 0; a < 100; a++)
{
   for(var i = 0; i < 100; i++)
   {
      var xx = x+i;
      var yy = y+a;
      if position_meeting(xx,yy,id) && !point_in_rectangle(x1,y1,x2,y2) {contained = false;}
   }
}
I also recommend making it a script!

Oh, and if the containment area itself isnt a simple shape, use another object and a second 'position_meeting' check...
Good idea.
If the object is small, I think this is a good solution.
 
If the object is small, I think this is a good solution.
It is a solution, but I think it would be a fairly expensive one compared to a pure maths solution (I did find one, and have included an example below)

I don't think it's just the cost of checking for the collision (position_meeting, doing point_in_rectangle is a simple comparison of less than / more than) but the fact you need two for loops to do it as well.

I come up with an idea of using tangent line to simulate ellipse.
First tangent line, then split the shape to triangles.
I'm not sure if this idea is easy to accomplish. Just an idea I want to share with you.
I don't know about tangent line, so it's really a question of whether you know the maths / thinking behind it. If you are able to figure through it, then a repeat loop and point_in_rectangle would probably cost less. It depends on how much maths you actually have to do to get these points:
new.png
You've got 7 points there to check, and it's somewhat more refined when it comes to cutting out the extra space when "redefining" the original shape.

Depends on:
1) If you know how to figure out these points
2) Just how much computation is required to do so
3) Whether it's still too imprecise to be accurate. In your example there's still two areas where it results in modest amounts of empty space (furthest left circle, furthest right circle)

You can figure out the arc of the ellipse (I'm cheating a little by knowing some dimensions here, as this is using a sprite of an ellipse for a visual comparison):
Code:
width = sprite_width / 2;
height = sprite_height / 2;

theta = 90;  // angle that will be decreased each loop
while (theta >= 0)
{
converted = degtorad(theta)
xx = x + width * cos(converted)
yy = y + height * sin(converted)
draw_line (x,y, xx, yy)
theta -= 1;
}
In the above code it will draw around the shape of the ellipse I have as a sprite. Though for reasons that are beyond me it is being calculated upside down. Here is an image:
ellipse.png
The green is the background of the room, so as to make it more visible. The blue / red is the original drawn sprite. The black lines are the drawn lines from the center to the calculated point.

As you can see, it is very accurate...albeit the "wrong way around". However, you can easily fix that, or work around it.

Taking the above WHILE loop: Now you know the point on the arc, if you duplicate this point three more times by reversing the xx / yy positions, you have four points around 360.

Then you check those four points for point_in_rectangle (or whatever object)

At a rough guess that would be less costly than any other type of check, due to using less loops (one loop , and is short in repeat number) and not needing pixel checking (or however it is that collision checking is done)

If the ellipse is angled then you'd have to account for that somewhere in the above code. Which, admittedly, I haven't yet considered how to do and am unsure if the calculation changes depending on the ellipses orientation:)

(I nicked it from an online maths tutorial - they are quite useful for finding the equations involved)

EDIT: I really, really, really (really!) can't figure out how to use this for angled ones.
new_two.png
Code:
is_end = 90;
theta = 90;
repeat (is_end)
 {
converted = degtorad(theta)
 xx = x + width * cos(converted)
 yy = y + height * sin(converted)
 dist = point_distance(x, y, xx, yy) // xx and yy are the wrong way around to begin with (as above)
angle = point_direction(x, y, xx, yy) // xx and yy are the wrong way around to begin with (as above)
xx = x + lengthdir_x(dist, angle + image_angle + 360); // 180 works too, though its always the wrong way around whichever I use
yy = y + lengthdir_y(dist, angle + image_angle + 360);
draw_line (x,y, xx, yy)
theta -= 1;
}
You can see it is matching an angled ellipse, but I don't understand converting the end result. This is a fudged outcome....it works, but I'd prefer knowing why :)
 
Last edited:

Neptune

Member
So I see people never fail to underestimate the power of modern CPUs.
The 'for' method I posted would work even if called 60x/s and with 10,000 checks per loop and not break a sweat with the average CPU. You could probably have 100 (1000?) of these objects running before starting to see an issue.

The point is, optimization in most cases, doesnt need to be nit-picked for CPU actions -- be aware of where issues MAY arise, and move on.
 
H

Homunculus

Guest
I'm really not good with this kind of stuff, so I apologize in advance if my idea is extremely dumb, BUT! couldn't something like this work?

- Create a surface (ideally same size as the view) and clear it with black
- Copy it into a buffer using buffer_get_surface
- Get the MD5 of the buffer
- Draw B with a white blend, and afterwards A with a black blend on the surface
- Again, copy the surface to the buffer, and get the MD5
- If the MD5 values are the same, it means that B is perfectly inside A

I call this "collision detection through MD5".

Having said that, I'll now run away and cry in a corner.
 
So I see people never fail to underestimate the power of modern CPUs.
The 'for' method I posted would work even if called 60x/s and with 10,000 checks per loop and not break a sweat with the average CPU. You could probably have 100 (1000?) of these objects running before starting to see an issue.

The point is, optimization in most cases, doesnt need to be nit-picked for CPU actions -- be aware of where issues MAY arise, and move on.
I'm not saying your suggestion is wrong, but for myself I want my code to be the best it can be. Anything that might be excessive is considered by me at the time of building it, because I'd rather not find out it's too bloated when everything is in full swing.

My project is a struggle with 30 objects, never mind a 100, or even a 1000. Depending on what you're making you can't be so carefree about your coding.

I didn't say what I said to be criticising you: it was a fine suggestion. But if djnjbjb finds they need to lessen the load, then (as far as I know) those nestled FOR loops will be heavier than you think. If there is, in fact, a cheaper alternative then that's just something you can't argue with. Better results are better results....but I'm not saying my suggestion is actually better, just that it might be. It would be interesting to compare them.

If it was me making this post I'd try out all the suggestions and stress test it with the debugger to see which performs best. In my experience it has been the case that smaller loops, and use of maths, works out better than multiple loops with seemingly "lesser" demands. If that turns out to be wrong I'm happy to admit to it, as I still learned something from looking into this.

EDIT:
To be honest, on a smaller sprite your method is around 100 microseconds faster. But the max figure of mine doesn't increase with the size of the ellipsis being checked, whereas the larger the area of the sprite being checked the more yours will increase.

@Homunculus
Sounds interesting. I'm curious to test this, and see how it fares.

@djnjbjb
Having tested my final solution, and Neptunes:
1) Two for loops and collision checks is 100 microseconds faster than my suggestion, but only for a small sprite (the one I checked was 32 width by 14 height)

2) Mine doesn't increase when checking a larger area. I checked it on a much bigger sprite, and there wasn't much difference. Neptunes solution took a lot longer under the same circumstances, due to the for loops increasing in size.

My effort is super accurate and scales for any size, but at smaller sizes the alternative method of two for loops is marginally faster.
 
Last edited:

Neptune

Member
Lol the buffer way sounds interesting, but also not really necessary xD

I appreciate your math the_dude, but if we must make a comparison, the math will quickly become unmanageable with complex shapes (for the bounded or the bounder)...
And talking in microseconds or low milliseconds for 95% of gamedev is pointless with modern technology.
 
And talking in microseconds or low milliseconds for 95% of gamedev is pointless with modern technology.
Yeah, but.....it didn't take much difference for the "lesser" method to end up being almost 10 times longer, when a 32 by 14 sprite was scaled up to twice the size. 10 times slower! And that's just checking one instance. I wouldn't shrug off that amount of increase for such a basic thing. But each to their own :)
 
Top