1

I want to check if a Rectangle(A Player) , intersects with one of the rectangles in a list (List).

I am currently using a for loop , which makes it slow , and poor performance.

for (int i = 0; i < gameObjects.objectList.Count; i++)
{
   if (gameObjects.objectList[i].IntersectsWith(gameObjects.player))
   {
        gameObjects.objectList.RemoveAt(i); // if the player collided with an object, remove that object
    }
}

How can I make it more efficient / is there another way to do it faster?

dandan78
  • 13,328
  • 13
  • 64
  • 78
Paz Haviv
  • 137
  • 1
  • 1
  • 10
  • A for-loop is not slow, but this code is wrong. – Dennis_E Nov 20 '15 at 15:38
  • What's the nature of the list? Are the rectangles in the list statically defined? Might they overlap each other and, if so, what result should be expected? – Damien_The_Unbeliever Nov 20 '15 at 15:39
  • @Dennis_E , can you please explain whats wrong? I would really like to know so I can fix it – Paz Haviv Nov 20 '15 at 15:42
  • @Damien_The_Unbeliever , I am creating a new rectangle every X time , adding it to the list , and then drawing it. there is some code missing in the for loop , actually just a line , that says draw that object to the bitmap. – Paz Haviv Nov 20 '15 at 15:43
  • @PazHaviv It's wrong iterate through a collection and remove elements from it at the same time. This code should throw an exception. – Mark Shevchenko Nov 20 '15 at 15:44
  • The problem is that you remove items from the list you iterate which means you'll end up skipping the next rectangle when you do that. For example if you delete index 3 then the item at 4 will now the at index 3, but on the next loop you look at index 4 which is now whatever was at 5. – juharr Nov 20 '15 at 15:45
  • @MarkShevchenko It will not throw an exception becuase it is not using `foreach` which would get an `IEnumerator` that would be invalidated when the collection is modified. – juharr Nov 20 '15 at 15:46
  • If you have 2 rectangles at index n and n+1, you want to remove them both, but because of `i++`, the 2nd one will be skipped. – Dennis_E Nov 20 '15 at 15:46
  • @MarkShevchenko , hmm ok then. how can I remove it from the list , while iterating through it? , and that aside , how can I improve it , because it lowers the FPS by 2 – Paz Haviv Nov 20 '15 at 15:47
  • @juharr hmm ok then. how can I remove it from the list , while iterating through it? – Paz Haviv Nov 20 '15 at 15:47
  • 1
    Simply start at the end instead of the front: `for (int i = gameObjects.objectList.Count - 1; i >= 0; i--)` – Dennis_E Nov 20 '15 at 15:48
  • @PazHaviv Typically you'd start at the end and decrement. Or you can do a `i--;` after you do a `Remove`. – juharr Nov 20 '15 at 15:48
  • @Dennis_E , so basically start at the end index of that list , and go down? like from index 5 to index 0? – Paz Haviv Nov 20 '15 at 15:49
  • @juharr ok. now that aside . how can I improve the collision detection? because that entire for loop reduces the game's fps by 2 – Paz Haviv Nov 20 '15 at 15:50
  • How is `IntersectsWith` implemented? – wingerse Nov 20 '15 at 15:51
  • @EmpereurAiman , what do you mean by how its being implemented? – Paz Haviv Nov 20 '15 at 15:52
  • I mean how is it written? How does the method check if they are colliding? – wingerse Nov 20 '15 at 15:55
  • @EmpereurAiman https://msdn.microsoft.com/en-us/library/system.drawing.rectangle.intersectswith(v=vs.110).aspx – TheLethalCoder Nov 20 '15 at 15:56
  • I thought OP was using a custom implementation of the Rectangle class. – wingerse Nov 20 '15 at 15:58
  • @EmpereurAiman maybe but I just provided that seeing as I was already looking at the docs – TheLethalCoder Nov 20 '15 at 15:59
  • do you need to compare with all items in the list or just first Intrsects? – makison Nov 20 '15 at 16:01
  • @AlekseyNosik I just need to check if for intersection – Paz Haviv Nov 20 '15 at 16:05
  • @EmpereurAiman its the built in rectangle class – Paz Haviv Nov 20 '15 at 16:06
  • @Paz Haviv below ansers that you should change logic at all, and looks they are right, but if we are talking about your for loop you should add return just after item was removed to go out from for loop, for loop is fastest approach here is my research with numbers http://stackoverflow.com/questions/33787962/how-do-i-check-if-int-contains-only-certain-numbers/33788318#33788318 – makison Nov 20 '15 at 16:10
  • @AlekseyNosik thanks ill look into that , but I think I will go with the K-D-Tree option. – Paz Haviv Nov 20 '15 at 16:12

3 Answers3

1

You can try organize your rectangles in a structure called k-d-tree.

It gives you up to O(log N) complexity in a large rectangle array (> 100).

F.e. make binary tree with fixed length, say, 2. Divide your space into left and right halves, then each half divide into top and bottom quarters (and so on).

Inside leaf node create a list on rectangles. If a rectangles falls into left half and top quarter, locate it in the list of this quarter.

A rectangle may be locates in a few list at the same time (f.e. in case if it falls in left and right halves).

To check intersection you should test rectangle in responding halves and quarters.

Or, you removes too many rectangles, it's faster to copy remaining rectangles into the new list in your own code.

Small example.

public enum CheckBy
{
    Horizontal,

    Vertical
}

public class Node
{
    public Node First { get; set; }

    public Node Second { get; set; }

    public int Coordinate { get; set; }

    public CheckBy CheckBy { get; set; }

    public List<Rectangle> Rectangles { get; set; }
}

public bool IsRectangleInFist(Node node, Rectangle rectangle)
{
    if (node.CheckBy == CheckBy.Horizontal)
        return rectangle.Left <= node.Coordinate;

    return rectangle.Top <= node.Coordinate;
}

public bool IsRectangelInSecond(Node node, Rectangle rectangle)
{
    if (node.CheckBy == CheckBy.Horizontal)
        return rectangle.Right >= node.Coordinate;

    return rectangle.Bottom >= node.Coordinate;
}

public void AddRectangleInSuitableNode(Node node, Rectangle rectangle)
{
    if (InRectangleInFirst(node, rectangle))
        AddRectangleInSuitableNode(node.First, rectangle);

    if (InRectangleInSecond(node, rectangle))
        AddRectangleInSuitableNode(node.Second, rectangle);
}

public void SearchIntersectedRectangles(Node node, Rectangle rectangle, List<Rectangles> result)
{
    // If not-leaf node
    if (node.Rectangles == null && node.First != null && node.Second != null)
    {
        if (IsRectangleInFirst(node, rectangle))
            SearchIntersecatedRectangles(node.First, rectangle, result);

        if (IsRectangleInSecond(node, rectangle))
            SearchIntersecatedRectangles(node.Second, rectangle, result);

        return;
    }

    result.AddRangle(Rectangles.Where(r => r.IsIntersect(rectangle)));
}

These all lines makes simple 2D-tree. First, make the tree:

// Say, all rectangles would be inside this "space"
const int leftest = -1000;
const int rightest = 1000;
const int bottomest = -1000;
const int toppest = 1000;

// Tree with depth == 2
var tree = new Node
{
    CheckBy = CheckBy.Hozirontal,
    Coordinate = (leftest + rightest)/2,
    First = new Node
    {
        CheckBy = CheckBy.Vertical,
        Coordintate = (toppest + bottomest)/2,
        Rectangles = new List<Rectangle>(),
    },
    Second = new Node
    {
        CheckBy = CheckBy.Vertical,
        Coordintate = (toppest + bottomest)/2,
        Rectangles = new List<Rectangle>(),
    },
}

Then, sort all rectangles in this tree:

foreach (var rectangle in rectangles)
    AddRectangleInSuitableNode(tree, rectangle);

Now you can fast get intersecting rectangles:

var intersecting = new List<Rectangles>();
SearchIntersecatedRectangles(tree, targetRectangle, intersecting);
// Here you can remove intersecting rectangles...
Mark Shevchenko
  • 7,937
  • 1
  • 25
  • 29
0

Basically, you need to stop checking all of the rectangles every time. You need to somehow figure out which rectangles are located in the vicinity of the player.

You could use some kind of spatial grid to store your rectangles so you could quickly find adjacent rectangles to be checked for collision. See this tutorial for example: N Tutorial B - Broad-Phase Collision.

Michael Antipin
  • 3,522
  • 17
  • 32
  • I can create a grid and then check if that grid contains objects and if they intersect with the player , but I doubt it will make any difference , since you need to check every time if the grid the player is in , contains an object. so basically it will all end up in poor performance – Paz Haviv Nov 20 '15 at 16:08
0

I doubt it will be faster but you can always do it with Ling in a one liner:

gameObjects.objectList = gameObjects.objectList
                         .Select(go => go)
                         .Where(go => !go.IntersectsWith(gameObjects.player))
                         .ToList();

This essentially sets the list to one where any gameObject that collides with player is removed.

Also note that it is usually faster to process a sorted list first, so doing this:

gameObjects.objectList = gameObjects.objectList
                         .OrderBy(go => go.X)
                         .ThenBy(go => go.Y)
                         .ToList();

may help speed things up a bit. Doing this ordering every frame will be slow though so it will be worth ordering the objects as they are added to the list.

TheLethalCoder
  • 6,668
  • 6
  • 34
  • 69