1 module collision; 2 3 // 4 // Copyright (C) 2015, by Laszlo Szeremi under the Boost license. 5 // 6 // VDP Engine, collision detector module 7 8 import std.conv; 9 import std.stdio; 10 import std.algorithm; 11 //import std.bitmanip; 12 13 import graphics.sprite; 14 import graphics.bitmap; 15 import system.etc; 16 import system.advBitArray; 17 18 import graphics.layers; 19 /* 20 *Use this interface to listen to collision events. 21 */ 22 public interface CollisionListener{ 23 //Invoked when two sprites have collided. 24 //IMPORTANT: Might generate "mirrored collisions" if both sprites are moving. Be aware of it when you're developing your program. 25 public void spriteCollision(CollisionEvent ce); 26 27 public void backgroundCollision(CollisionEvent ce); 28 } 29 /* 30 *Used with the BackgroundDetector. 31 */ 32 public struct TileSource{ 33 public int x, y; 34 public wchar id; 35 this(int xx, int yy, wchar idid){ 36 x = xx; 37 y = yy; 38 id = idid; 39 } 40 } 41 /** 42 * After 0.9.1, sprite collisions will no longer be detected from bitmaps, but instead from CollisionModels. 43 * Uses Bitarrays for faster detection, custom shapes are possible via XMPs. 44 */ 45 public class CollisionModel{ 46 protected AdvancedBitArray ba; 47 protected int iX, iY; 48 49 this(int x, int y, AdvancedBitArray b){ 50 51 iX = x; 52 iY = y; 53 ba = b; 54 55 } 56 public bool getBit(int x, int y){ 57 return ba[x+(iX*y)]; 58 } 59 /// Creates a default collision model from a Bitmap32Bit, by pixel transparency. 60 61 /// Creates a default collision model from a Bitmap16Bit, by pixel index. 62 } 63 /** 64 *Sprite to sprite collision detector. Collision detection is invoked when a sprite is moving, thus only testing sprites that are moving with all other sprites on the list. It tests rows to each other istead of pixels to speed up the process. 65 */ 66 public class CollisionDetector : SpriteMovementListener{ 67 //private Collidable[string] cList; 68 private CollisionListener[] cl; 69 public ISpriteCollision source; 70 //private Bitmap16Bit[int] sourceS; 71 private CollisionModel[int] collisionModels; 72 private Coordinate[int] sourceC; 73 private FlipRegister[int] sourceFR; 74 //private int[int] sourceSC; 75 private ushort sourceTI; 76 public this(){ 77 } 78 /* 79 * Adds a CollisionModel. Make sure you match the index on the SpriteLayer and replace the CollisionModel alongside with the sprite 80 */ 81 public void addCollisionModel(CollisionModel c, int i){ 82 collisionModels[i] = c; 83 } 84 85 public void removeCollisionModel(int i){ 86 collisionModels.remove(i); 87 } 88 89 //Adds a CollisionListener to its list. c: the CollisionListener you want to add. s: an ID. 90 public void addCollisionListener(CollisionListener c){ 91 cl[] = c; 92 } 93 //Removes a CollisionListener based on the ID 94 public void removeCollisionListener(string s){ 95 cl.remove(s); 96 } 97 98 /// Implemented from the SpriteMovementListener interface, invoked when a sprite moves. 99 /// Tests the sprite that invoked it with all other in its list. 100 public void spriteMoved(int ID){ 101 //sourceS = source.getSpriteSet(); 102 sourceC = source.getCoordinates(); 103 sourceFR = source.getFlipRegisters(); 104 //sourceSC = source.getSpriteSorter; 105 //sourceTI = source.getTransparencyIndex(); 106 107 foreach(int i ; collisionModels.byKey()){ 108 if(ID == i){ 109 continue; 110 } 111 if(testCollision(i, ID)){ 112 invokeCollisionEvent(ID, i); 113 } 114 115 116 } 117 } 118 /// Tests if the two objects have collided. Returns true if they had. Pixel precise. 119 public bool testCollision(int a, int b){ 120 Coordinate ca = sourceC[a]; // source 121 Coordinate cb = sourceC[b]; // destiny 122 123 // if two sprites don't overlap, then it won't test any further 124 if (ca.bottom <= cb.top) return false; 125 if (ca.top >= cb.bottom) return false; 126 127 if (ca.right <= cb.left) return false; 128 if (ca.left >= cb.right) return false; 129 130 Coordinate cc; // test area coordinates 131 int cTPA, cTPB; // testpoints 132 133 // process the test area and calculate the test points 134 if(ca.top >= cb.top){ 135 cc.top = cb.top; 136 cTPA = ca.getXSize() * (ca.top - cb.top); 137 }else{ 138 cc.top = ca.top; 139 cTPB = cb.getXSize() * (cb.top - ca.top); 140 } 141 if(ca.bottom >= cb.bottom){ 142 cc.bottom = cb.bottom; 143 }else{ 144 cc.bottom = ca.bottom; 145 } 146 if(ca.left >= cb.left){ 147 cc.left = cb.left; 148 cTPA += ca.left - cb.left; 149 }else{ 150 cc.left = ca.left; 151 cTPB += cb.left - ca.left; 152 } 153 if(ca.right >= cb.right){ 154 cc.right = cb.right; 155 }else{ 156 cc.right = ca.right; 157 } 158 159 for(int y ; y < cc.getYSize ; y++){ 160 if(collisionModels[a].ba.test(cTPA, cc.getXSize, collisionModels[b].ba, cTPB)){ 161 return true; 162 } 163 cTPA += ca.getXSize(); 164 cTPB += cb.getXSize(); 165 } 166 167 return false; 168 } 169 //Invokes the collision events on all added CollisionListener. 170 private void invokeCollisionEvent(int a, int b){ 171 foreach(e; cl){ 172 e.spriteCollision(a, b); 173 } 174 } 175 } 176 /** 177 *Collision detector without the pixel precision function. 178 */ 179 public class QCollisionDetector : CollisionDetector{ 180 public this(){ 181 super(); 182 } 183 public override bool testCollision(int a, int b){ 184 Coordinate ca = sourceC[a]; 185 Coordinate cb = sourceC[b]; 186 187 if (ca.bottom <= cb.top) return false; 188 if (ca.top >= cb.bottom) return false; 189 190 if (ca.right <= cb.left) return false; 191 if (ca.left >= cb.right) return false; 192 return true; 193 } 194 } 195 /* 196 *Tests for background/sprite collision. Preliminary. 197 *IMPORTANT! Both layers have to have the same scroll values, or else odd things will happen. 198 */ 199 public class BackgroundTester : SpriteMovementListener{ 200 private ITileLayer blSource; 201 private Bitmap16Bit[wchar] blBMP; 202 private wchar[] mapping; 203 public wchar[] ignoreList; 204 private ISpriteCollision slSource; 205 private BLInfo blInfo; 206 private CollisionListener[] cl; 207 208 public this(ITileLayer bl, ISpriteCollision sl){ 209 blSource = bl; 210 slSource = sl; 211 blInfo = blSource.getLayerInfo(); 212 } 213 214 public void addCollisionListener(CollisionListener c){ 215 cl ~= c; 216 } 217 218 public void spriteMoved(int ID){ 219 /*mapping = blSource.getMapping(); 220 Coordinate spritePos = slSource.getCoordinates()[ID]; 221 222 int x1 = (spritePos.left-(spritePos.left%blInfo.tileX))/blInfo.tileX, y1 = (spritePos.ya-(spritePos.ya%blInfo.tileY))/blInfo.tileY, 223 x2 = (spritePos.xb-(spritePos.xb%blInfo.tileX))/blInfo.tileX, y2 = (spritePos.yb-(spritePos.yb%blInfo.tileY))/blInfo.tileY; 224 TileSource[] ts; 225 if(x1 >= 0 && y1 >= 0){ 226 for(int y = y1; y <= y2 ; y++){ 227 228 for(int x = x1 ; x <= x2 ; x++){ 229 if(spritePos.getXSize <= x * blInfo.tileX){ 230 wchar idT = mapping[x+(blInfo.mX*y)]; 231 232 if(!canFind(ignoreList, idT)){ 233 //writeln(idT); 234 ts ~= TileSource(x, y, idT); 235 } 236 } 237 } 238 } 239 } 240 if(ts.length > 0) 241 invokeCollisionEvent(ID, ts);*/ 242 } 243 private void invokeCollisionEvent(int spriteSource, TileSource[] tileSources){ 244 foreach(c; cl){ 245 c.backgroundCollision(spriteSource, tileSources); 246 } 247 } 248 } 249 public class CollisionEvent{ 250 public int sourceA, sourceB, hitboxA, hitboxB; /// A = object that called the detection. 251 public Coordinate posA, posB; 252 public wchar[] top, bottom, left, right; 253 254 public this(int sourceA, int sourceB, int hitboxA, int hitboxB, Coordinate posA, Coordinate posB){ 255 this.sourceA = sourceA; 256 this.sourceB = sourceB; 257 this.hitboxA = hitboxA; 258 this.hitboxB = hitboxB; 259 this.posA = posA; 260 this.posB = posB; 261 } 262 }