1 module prova.collision.spacialmap2d; 2 3 import prova.attachables; 4 import prova.collision; 5 import prova.core; 6 import prova.graphics; 7 import prova.math; 8 import prova.util; 9 import std.math; 10 11 /// 12 class SpacialMap2D 13 { 14 /// 15 int bucketPadding = 5; 16 private Vector2 bucketSize; 17 private LinkedList!(Collider2D)[Vector2] map; 18 private Collider2D[] colliders; 19 private LinkedList!(Collider2D[2]) collisions; 20 21 /// 22 this() 23 { 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 ~= collider; 138 collider.spacialMap = this; 139 140 updateBucketSize(collider); 141 } 142 143 /// Should not be called in most circumstances 144 void add(Collider2D[] colliders) 145 { 146 foreach(Collider2D collider; colliders) { 147 this.colliders ~= collider; 148 collider.spacialMap = this; 149 150 updateBucketSize(collider); 151 } 152 } 153 154 /// Should not be called in most circumstances 155 void remove(Collider2D collider) 156 { 157 colliders = colliders.removeElement(collider); 158 collider.spacialMap = null; 159 160 updateBucketSize(); 161 } 162 163 /// Should not be called in most circumstances 164 void remove(Collider2D[] colliders) 165 { 166 foreach(Collider2D collider; colliders) { 167 this.colliders = this.colliders.removeElement(collider); 168 collider.spacialMap = null; 169 } 170 171 updateBucketSize(); 172 } 173 174 private void updateBucketSize() 175 { 176 bucketSize.set(0, 0); 177 178 foreach(Collider2D collider; colliders) 179 updateBucketSize(collider); 180 } 181 182 package(prova) void updateBucketSize(Collider2D collider) 183 { 184 const Vector2 size = collider.getSize(); 185 186 if(size.x + bucketPadding * 2 > bucketSize.x) 187 bucketSize.x = size.x + bucketPadding * 2; 188 if(size.y + bucketPadding * 2 > bucketSize.y) 189 bucketSize.y = size.y + bucketPadding * 2; 190 } 191 }