1 module PixelPerfectEngine.collision;
2 
3 /*
4  * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license.
5  *
6  * Pixel Perfect Engine, collision detector module
7  */
8 
9 import std.conv;
10 import std.stdio;
11 import std.algorithm;
12 //import std.bitmanip;
13 
14 import PixelPerfectEngine.graphics.bitmap;
15 import PixelPerfectEngine.system.etc;
16 import PixelPerfectEngine.system.advBitArray;
17 
18 import PixelPerfectEngine.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(new CollisionEvent(a,b,a,b,sourceC[a],sourceC[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 
245 	}
246 }
247 public class CollisionEvent{
248 	public int sourceA, sourceB, hitboxA, hitboxB;		/// A = object that called the detection.
249 	public Coordinate posA, posB;
250 	public wchar[] top, bottom, left, right;
251 
252 	public this(int sourceA, int sourceB, int hitboxA, int hitboxB, Coordinate posA, Coordinate posB){
253 		this.sourceA = sourceA;
254 		this.sourceB = sourceB;
255 		this.hitboxA = hitboxA;
256 		this.hitboxB = hitboxB;
257 		this.posA = posA;
258 		this.posB = posB;
259 	}
260 }