1 module prova.collision.spacialmap2d;
2 
3 import prova.collision,
4        prova.core,
5        prova.graphics,
6        prova.math,
7        prova.util,
8        std.math;
9 
10 ///
11 class SpacialMap2D
12 {
13   ///
14   int bucketPadding = 5;
15   private Vector2 bucketSize;
16   private LinkedList!(Collider2D)[Vector2] map;
17   private LinkedList!(Collider2D) colliders;
18   private LinkedList!(Collider2D[2]) collisions;
19 
20   ///
21   this()
22   {
23     colliders = new LinkedList!(Collider2D);
24     collisions = new LinkedList!(Collider2D[2]);
25   }
26 
27   ///
28   void mapColliders()
29   {
30     foreach(Collider2D collider; colliders)
31     {
32       // we map the corners to a space,
33       // edges don't matter as the buckets are
34       // as big as the largest collider
35       Rect bounds = collider.getBounds();
36 
37       mapColliderCorner(bounds.getTopLeft(), collider);
38       mapColliderCorner(bounds.getTopRight(), collider);
39       mapColliderCorner(bounds.getBottomLeft(), collider);
40       mapColliderCorner(bounds.getBottomRight(), collider);
41 
42       collider.previousCollisions = collider.collisions;
43       collider.collisions = new LinkedList!Collider2D;
44     }
45   }
46 
47   private void mapColliderCorner(Vector2 corner, Collider2D collider)
48   {
49     Vector2 key = Vector2(
50       floor(corner.x / bucketSize.x),
51       floor(corner.y / bucketSize.y)
52     );
53 
54     if(!(key in map))
55       map[key] = new LinkedList!(Collider2D);
56 
57     LinkedList!Collider2D bucket = map[key];
58 
59     if(!bucket.contains(collider))
60       map[key].insertBack(collider);
61   }
62 
63   ///
64   void markCollisions()
65   {
66     // loop through buckets in the spacial map
67     foreach(LinkedList!Collider2D bucket; map.values)
68     {
69       searchBucket(bucket);
70       
71       bucket.clear();
72     }
73 
74     map.clear();
75   }
76 
77   private void searchBucket(LinkedList!Collider2D bucket)
78   {
79     // test for collision with every collider within the bucket
80     foreach(Node!Collider2D nodeA; bucket)
81     {
82       Collider2D colliderA = nodeA.value;
83 
84       // test with colliders that haven't already been tested with every collider
85       // by starting after the current collider being tested
86       for(Node!Collider2D nodeB = nodeA.getNext(); nodeB; nodeB = nodeB.getNext())
87       {
88         Collider2D colliderB = nodeB.value;
89 
90         if(colliderA.intersects(colliderB) && !colliderA.collisions.contains(colliderB))
91           markCollision(colliderA, colliderB);
92       }
93     }
94   }
95 
96   private void markCollision(Collider2D colliderA, Collider2D colliderB)
97   {
98     colliderA.collisions.insertBack(colliderB);
99     colliderB.collisions.insertBack(colliderA);
100 
101     Collider2D[2] collision = [colliderA, colliderB];
102     collisions.insertBack(collision);
103   }
104 
105   ///
106   void resolveCollisions()
107   {
108     foreach(Collider2D[2] collision; collisions)
109       resolveCollision(collision[0], collision[1]);
110 
111     foreach(Collider2D colliderA; colliders)
112       foreach(Collider2D colliderB; colliderA.previousCollisions)
113         colliderA.entity.onCollisionExit2D(colliderA, colliderB);
114 
115     collisions.clear();
116   }
117 
118   private void resolveCollision(Collider2D colliderA, Collider2D colliderB)
119   {
120     if(!colliderA.previousCollisions.contains(colliderB)) {
121       colliderA.entity.onCollisionEnter2D(colliderA, colliderB);
122       colliderB.entity.onCollisionEnter2D(colliderB, colliderA);
123     }
124 
125     colliderA.entity.onCollision2D(colliderA, colliderB);
126     colliderB.entity.onCollision2D(colliderB, colliderA);
127 
128     // remove the collision from the previous list if it exists,
129     // to speed up the collision exit loop
130     colliderA.previousCollisions.remove(colliderB);
131     colliderB.previousCollisions.remove(colliderA);
132   }
133 
134   /// Should not be called in most circumstances
135   void add(Collider2D collider)
136   {
137     colliders.insertBack(collider);
138 
139     updateBucketSize(collider);
140   }
141 
142   /// Should not be called in most circumstances
143   void add(LinkedList!Collider2D colliders)
144   {
145     foreach(Collider2D collider; colliders) {
146       this.colliders.insertBack(collider);
147 
148       updateBucketSize(collider);
149     }
150   }
151 
152   /// Should not be called in most circumstances
153   void remove(Collider2D collider)
154   {
155     colliders.remove(collider);
156 
157     updateBucketSize();
158   }
159 
160   /// Should not be called in most circumstances
161   void remove(LinkedList!Collider2D colliders)
162   {
163     foreach(Collider2D collider; colliders)
164       colliders.remove(collider);
165 
166     updateBucketSize();
167   }
168 
169   private void updateBucketSize()
170   {
171     bucketSize.set(0, 0);
172 
173     foreach(Collider2D collider; colliders)
174       updateBucketSize(collider);
175   }
176 
177   private void updateBucketSize(Collider2D collider)
178   {
179     const Vector2 size = collider.getSize();
180 
181     if(size.x + bucketPadding * 2 > bucketSize.x)
182       bucketSize.x = size.x + bucketPadding * 2;
183     if(size.y + bucketPadding * 2 > bucketSize.y)
184       bucketSize.y = size.y + bucketPadding * 2;
185   }
186 }