{"id":94,"date":"2015-08-03T10:00:17","date_gmt":"2015-08-03T01:00:17","guid":{"rendered":"https:\/\/www.rapapaing.com\/blog\/?p=94"},"modified":"2020-02-02T17:42:18","modified_gmt":"2020-02-02T08:42:18","slug":"practical-2d-collision-detection-part-2","status":"publish","type":"post","link":"https:\/\/rapapaing.com\/blog\/2015\/08\/practical-2d-collision-detection-part-2\/","title":{"rendered":"Practical 2D collision detection \u2013 Part 2"},"content":{"rendered":"\n<p>In our last article, we made a very simple program that helped us detect when two circles were colliding. However, 2D games are usually much more complex than just circles. I shall now introduce the next shape: the rectangle. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"751\" height=\"611\" src=\"https:\/\/www.rapapaing.com\/blog\/wp-content\/uploads\/2020\/02\/rectangle1.png\" alt=\"\" class=\"wp-image-98\"\/><\/figure>\n\n\n\n<p>By now you probably noticed that, for the screenshots I\u2019m using a program called \u201cCollision Test\u201d. This is a small tool I made to help me visualize all this stuff I\u2019m talking about. I used this program to build the collision detection\/resolution framework for an indie top-down adventure game I was involved in. I will be talking more about this tool in future articles.<\/p>\n\n\n\n<p>Now, there are many ways to represent a rectangle. I will be representing them as five numbers: The center coordinates, width, height and the rotation angle:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public class CollisionRectangle\n{\n    public float X { get; set; }\n    public float Y { get; set; }\n    public float Width { get; set; }\n    public float Height { get; set; }\n    public float Rotation { get; set; }\n\n    public CollisionRectangle(float x, float y, float width, float height, float rotation)\n    {\n        X = x;\n        Y = y;\n        Width = width;\n        Height = height;\n        Rotation = rotation\n    }\n}<\/pre>\n\n\n\n<p>Now, for our first collision, we will collide a circle and a rectangle. There are two types of collisions to consider: When the circle is entirely inside the rectangle\u2026 <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"751\" height=\"611\" src=\"https:\/\/www.rapapaing.com\/blog\/wp-content\/uploads\/2020\/02\/rectangle_inside.png\" alt=\"\" class=\"wp-image-96\"\/><\/figure>\n\n\n\n<p>\u2026And when the circle is partly inside the rectangle, that is, it is touching the border<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"751\" height=\"611\" src=\"https:\/\/www.rapapaing.com\/blog\/wp-content\/uploads\/2020\/02\/rectangle_intersection.png\" alt=\"\" class=\"wp-image-97\"\/><\/figure>\n\n\n\n<p>These are two different types of collisions, and use different algorithms to determine whether or not there is a collision.<\/p>\n\n\n\n<p>But first, let\u2019s forget about the rectangle\u2019s position and rotation. Our first approach will deal with a rectangle centered in the world, and not rotated:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"751\" height=\"611\" src=\"https:\/\/www.rapapaing.com\/blog\/wp-content\/uploads\/2020\/02\/rectangle_centered.png\" alt=\"\" class=\"wp-image-95\"\/><\/figure>\n\n\n\n<p>Under these constraints, the circle is inside the rectangle when both the X coordinate of the circle is between the left and right borders, and the Y coordinate is between the top and bottom borders, like so:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public static bool IsCollision(CollisionCircle a, CollisionRectangle b)\n{\n    \/\/ For now, we will suppose b.X==0, b.Y==0 and b.Rotation==0\n    float halfWidth = b.Width \/ 2.0f;\n    float halfHeight = b.Height \/ 2.0f;\n    if (a.X >= -halfWidth &amp;&amp; a.X &lt;= halfWidth &amp;&amp; a.Y >= -halfHeight &amp;&amp; a.Y &lt;= halfHeight)\n    {\n        \/\/ Circle is inside the rectangle\n        return true;\n    }\n    return false; \/\/ We're not finished yet...\n}<\/pre>\n\n\n\n<p>But this is not enough. This only works when the center of the circle is inside the rectangle. There are plenty of situations where the center of the circle is outside the rectangle, but the circle is still touching the rectangle.<\/p>\n\n\n\n<p>In this case, we first find the point in the rectangle which is closest to the circle, and if the distance between this point and the center of the circle is smaller than the radius, then the circle is touching the border of the rectangle.<\/p>\n\n\n\n<p> We find the closest point for the X and Y coordinates separately:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">float closestX, closestY;\n\n\/\/ Find the closest point in the X axis\nif (a.X &lt; -halfWidth) closestX = -halfWidth; else if (a.X > halfWidth)\n    closestX = halfWidth\nelse\n    closestX = a.X;\n\n\/\/ Find the closest point in the Y axis\nif (a.Y &lt; -halfHeight) closestY = -halfHeight; else if (a.Y > halfHeight)\n    closestY = halfHeight;\nelse\n    closestY = a.Y;<\/pre>\n\n\n\n<p>And now we bring it all together:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public static bool IsCollision(CollisionCircle a, CollisionRectangle b)\n{\n    \/\/ For now, we will suppose b.X==0, b.Y==0 and b.Rotation==0\n    float halfWidth = b.Width \/ 2.0f;\n    float halfHeight = b.Height \/ 2.0f;\n\n    if (a.X >= -halfWidth &amp;&amp; a.X &lt;= halfWidth &amp;&amp; a.Y >= -halfHeight &amp;&amp; a.Y &lt;= halfHeight)\n    {\n        \/\/ Circle is inside the rectangle\n        return true;\n    }\n\n    float closestX, closestY;\n    \/\/ Find the closest point in the X axis\n    if (a.X &lt; -halfWidth) closestX = -halfWidth; else if (a.X > halfWidth)\n        closestX = halfWidth\n    else\n        closestX = a.X;\n\n    \/\/ Find the closest point in the Y axis\n    if (a.Y &lt; -halfHeight) closestY = -halfHeight; else if (a.Y > halfHeight)\n        closestY = halfHeight;\n    else\n        closestY = a.Y;\n\n    float deltaX = a.X - closestX;\n    float deltaY = a.Y - closestY;\n    float distanceSquared = deltaX * deltaX - deltaY * deltaY;\n\n    if (distanceSquared &lt;= a.R * a.R)\n        return true;\n\n    return false;\n}<\/pre>\n\n\n\n<p>Looks good, but we\u2019re still operating under the assumption that the rectangle is centered and not rotated.<\/p>\n\n\n\n<p>To overcome this limitation, we can move the entire world -that is, both the rectangle and the circle-, so the rectangle ends centered and non-rotated:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"499\" height=\"298\" src=\"https:\/\/www.rapapaing.com\/blog\/wp-content\/uploads\/2020\/02\/rotated.png\" alt=\"\" class=\"wp-image-99\"\/><\/figure>\n\n\n\n<p>In other words, we have to find the position of the circle, relative to the rectangle. This is pretty straightforward trigonometry:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">float relativeX = a.X - b.X;\nfloat relativeY = a.Y - b.Y;\nfloat relativeDistance = (float)Math.Sqrt(relativeX * relativeX + relativeY * relativeY);\nfloat relativeAngle = (float)Math.Atan2(relativeY, relativeX);\nfloat newX = relativeDistance * (float)Math.Cos(relativeAngle - b.Rotation);\nfloat newY = relativeDistance * (float)Math.Sin(relativeAngle - b.Rotation);<\/pre>\n\n\n\n<p> And then put it all together: <\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public class CollisionRectangle\n{\n    public float X { get; set; }\n    public float Y { get; set; }\n    public float Width { get; set; }\n    public float Height { get; set; }\n    public float Rotation { get; set; }\n\n    public CollisionRectangle(float x, float y, float width, float height, float rotation)\n    {\n        X = x;\n        Y = y;\n        Width = width;\n        Height = height;\n        Rotation = rotation\n    }\n\n    public static bool IsCollision(CollisionCircle a, CollisionRectangle b)\n    {\n        float relativeX = a.X - b.X;\n        float relativeY = a.Y - b.Y;\n        float relativeDistance = (float)Math.Sqrt(relativeX * relativeX + relativeY * relativeY);\n        float relativeAngle = (float)Math.Atan2(relativeY, relativeX);\n        float newX = relativeDistance * (float)Math.Cos(relativeAngle - b.Rotation);\n        float newY = relativeDistance * (float)Math.Sin(relativeAngle - b.Rotation);\n        float halfWidth = b.Width \/ 2.0f;\n        float halfHeight = b.Height \/ 2.0f;\n\n        if (newX >= -halfWidth &amp;&amp; newX &lt;= halfWidth &amp;&amp; newY >= -halfHeight &amp;&amp; newY &lt;= halfHeight)\n        {\n            \/\/ Circle is inside the rectangle\n            return true;\n        }\n\n        float closestX, closestY;\n        \/\/ Find the closest point in the X axis\n        if (newX &lt; -halfWidth) closestX = -halfWidth; else if (newX > halfWidth)\n            closestX = halfWidth\n        else\n            closestX = newX;\n\n        \/\/ Find the closest point in the Y axis\n        if (newY &lt; -halfHeight) closestY = -halfHeight; else if (newY > halfHeight)\n            closestY = halfHeight;\n        else\n            closestY = newY;\n\n        float deltaX = newX - closestX;\n        float deltaY = newY - closestY;\n        float distanceSquared = deltaX * deltaX - deltaY * deltaY;\n\n        if (distanceSquared &lt;= a.R * a.R)\n            return true;\n\n        return false;\n    }\n}\n\npublic class CollisionRectangle\n{\n    public float X { get; set; }\n    public float Y { get; set; }\n    public float Width { get; set; }\n    public float Height { get; set; }\n    public float Rotation { get; set; }\n\n    public CollisionRectangle(float x, float y, float width, float height, float rotation)\n    {\n        X = x;\n        Y = y;\n        Width = width;\n        Height = height;\n        Rotation = rotation\n    }\n\n    public static bool IsCollision(CollisionCircle a, CollisionRectangle b)\n    {\n        float relativeX = a.X - b.X;\n        float relativeY = a.Y - b.Y;\n        float relativeDistance = (float)Math.Sqrt(relativeX * relativeX + relativeY * relativeY);\n        float relativeAngle = (float)Math.Atan2(relativeY, relativeX);\n        float newX = relativeDistance * (float)Math.Cos(relativeAngle - b.Rotation);\n        float newY = relativeDistance * (float)Math.Sin(relativeAngle - b.Rotation);\n        float halfWidth = b.Width \/ 2.0f;\n        float halfHeight = b.Height \/ 2.0f;\n\n        if (newX >= -halfWidth &amp;&amp; newX &lt;= halfWidth &amp;&amp; newY >= -halfHeight &amp;&amp; newY &lt;= halfHeight)\n        {\n            \/\/ Circle is inside the rectangle\n            return true;\n        }\n\n        float closestX, closestY;\n        \/\/ Find the closest point in the X axis\n        if (newX &lt; -halfWidth) closestX = -halfWidth; else if (newX > halfWidth)\n            closestX = halfWidth\n        else\n            closestX = newX;\n\n        \/\/ Find the closest point in the Y axis\n        if (newY &lt; -halfHeight) closestY = -halfHeight; else if (newY > halfHeight)\n            closestY = halfHeight;\n        else\n            closestY = newY;\n\n        float deltaX = newX - closestX;\n        float deltaY = newY - closestY;\n        float distanceSquared = deltaX * deltaX - deltaY * deltaY;\n\n        if (distanceSquared &lt;= a.R * a.R)\n            return true;\n\n        return false;\n    }\n}<\/pre>\n\n\n\n<p> In the next article, we\u2019ll put some structure to all of this. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>In our last article, we made a very simple program that helped us detect when two circles were colliding. However, 2D games are usually much more complex than just circles. I shall now introduce the next shape: the rectangle. By now you probably noticed that, for the screenshots I\u2019m using a program called \u201cCollision Test\u201d. &hellip; <a href=\"https:\/\/rapapaing.com\/blog\/2015\/08\/practical-2d-collision-detection-part-2\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Practical 2D collision detection \u2013 Part 2&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[24,30,16],"class_list":["post-94","post","type-post","status-publish","format-standard","hentry","category-programming","tag-c-sharp","tag-collision-detection","tag-programming"],"_links":{"self":[{"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/posts\/94","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/comments?post=94"}],"version-history":[{"count":1,"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/posts\/94\/revisions"}],"predecessor-version":[{"id":100,"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/posts\/94\/revisions\/100"}],"wp:attachment":[{"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/media?parent=94"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/categories?post=94"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rapapaing.com\/blog\/wp-json\/wp\/v2\/tags?post=94"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}