1 module pixelperfectengine.collision.objectcollision;
2 
3 /*
4  * Copyright (C) 2015-2020, by Laszlo Szeremi under the Boost license.
5  *
6  * Pixel Perfect Engine, collision.objectCollision module.
7  */
8 
9 import collections.treemap;
10 import pixelperfectengine.collision.common;
11 //import pixelperfectengine.collision.boxCollision;
12 
13 /**
14  * Object-to-object collision detector.
15  */
16 public class ObjectCollisionDetector {
17 	ObjectMap			objects;	///Contains all of the objects 
18 	int					contextID;	///Stores the identifier of this detector
19 	/**
20 	 * The delegate where the events will be passed.
21 	 * Must be set up before using the collision detector.
22 	 */
23 	protected void delegate(ObjectCollisionEvent event)			objectToObjectCollision;
24 	/**
25 	 * Default CTOR that initializes the objectToObjectCollision, and contextID.
26 	 */
27 	public this (void delegate(ObjectCollisionEvent event) objectToObjectCollision, int contextID) @safe pure {
28 		assert(objectToObjectCollision !is null, "Delegate `objectToObjectCollision` must be a non-null value!");
29 		this.objectToObjectCollision = objectToObjectCollision;
30 		this.contextID = contextID;
31 	}
32 	/**
33 	 * Tests all shapes against each other
34 	 */
35 	public void testAll() {
36 		foreach (int iA, ref CollisionShape shA; objects) {
37 			testSingle(iA, &shA);
38 		}
39 	}
40 	/**
41 	 * Tests a single shape against the others
42 	 */
43 	public void testSingle(int objectID) {
44 		testSingle(objectID, objects.ptrOf(objectID));
45 	}
46 	///Ditto
47 	protected final void testSingle(int iA, CollisionShape* shA) {
48 		foreach (int iB, ref CollisionShape shB; objects) {
49 			if (iA != iB) {
50 				ObjectCollisionEvent event = testCollision(shA, &shB);
51 				if (event! is null) {
52 					event.idA = iA;
53 					event.idB = iB;
54 					objectToObjectCollision(event);
55 				}
56 			}
57 		}
58 	}
59 	/**
60 	 * Tests two objects. Calls cl if collision have happened, with the appropriate values.
61 	 */
62 	protected final ObjectCollisionEvent testCollision(CollisionShape* shA, CollisionShape* shB) pure {
63 		if (shA.position.bottom < shB.position.top || shA.position.top > shB.position.bottom || 
64 				shA.position.right < shB.position.left || shA.position.left > shB.position.right){
65 			//test if edge collision have happened with side edges
66 			if (shA.position.bottom >= shB.position.top && shA.position.top <= shB.position.bottom && 
67 					(shA.position.right - shB.position.left == -1 || shA.position.left - shB.position.right == 1)) {
68 				//calculate edge collision area
69 				Coordinate cc;
70 				if(shA.position.top >= shB.position.top)
71 					cc.top = shA.position.top;
72 				else
73 					cc.top = shB.position.top;
74 				if(shA.position.bottom >= shB.position.bottom)
75 					cc.bottom = shB.position.bottom;
76 				else
77 					cc.bottom = shA.position.bottom;
78 				if (shA.position.right < shB.position.left) {
79 					cc.left = shA.position.right;
80 					cc.right = shB.position.left;
81 				} else {
82 					cc.left = shB.position.right;
83 					cc.right = shA.position.left;
84 				}
85 				return new ObjectCollisionEvent(shA, shB, contextID, cc, ObjectCollisionEvent.Type.BoxEdge);
86 			} else if (shA.position.right >= shB.position.left && shA.position.left <= shB.position.right && 
87 					(shA.position.bottom - shB.position.top == -1 || shA.position.top - shB.position.bottom == 1)) {
88 				//calculate edge collision area
89 				Coordinate cc;
90 				if(shA.position.left >= shB.position.left)
91 					cc.left = shA.position.left;
92 				else
93 					cc.left = shB.position.left;
94 				if(shA.position.right >= shB.position.right)
95 					cc.right = shB.position.right;
96 				else
97 					cc.right = shA.position.right;
98 				if (shA.position.bottom < shB.position.top) {
99 					cc.top = shA.position.bottom;
100 					cc.bottom = shB.position.top;
101 				} else {
102 					cc.top = shB.position.bottom;
103 					cc.bottom = shA.position.top;
104 				}
105 				return new ObjectCollisionEvent(shA, shB, contextID, cc, ObjectCollisionEvent.Type.BoxEdge);
106 			} else return null;
107 		} else {
108 			//if there's a bitmap for both shapes, then proceed to per-pixel testing
109 			Coordinate ca, cb, cc; // test area coordinates
110 			//ca: Shape a's overlap area
111 			//cb: Shape b's overlap area
112 			//cc: global overlap area
113 			//
114 			if (shA.position.top <= shB.position.top) {
115 				ca.top = shB.position.top - shA.position.top;
116 				cc.top = shB.position.top;
117 			} else {
118 				cb.top = shA.position.top - shB.position.top;
119 				cc.top = shA.position.top;
120 			}
121 			if(shA.position.bottom <= shB.position.bottom) {
122 				cb.bottom = shA.position.bottom - shB.position.top;
123 				ca.bottom = shB.position.height - 1;
124 				cc.bottom = shA.position.bottom;
125 			} else {
126 				ca.bottom = shB.position.bottom - shA.position.top;
127 				cb.bottom = shA.position.height - 1;
128 				cc.bottom = shB.position.bottom; 
129 			}
130 			if (shA.position.left <= shB.position.left) {
131 				ca.left = shB.position.left - shA.position.left;
132 				cc.left = shB.position.left;
133 			} else {
134 				cb.left = shA.position.left - shB.position.left;
135 				cc.left = shA.position.left;
136 			}
137 			if (shA.position.right <= shB.position.right) {
138 				cb.right = shA.position.right - shB.position.left;
139 				ca.right = shB.position.width - 1;
140 				cc.right = shA.position.right;
141 			} else {
142 				ca.right = shB.position.right - shA.position.left;
143 				cb.right = shA.position.width - 1;
144 				cc.right = shB.position.right;
145 			}
146 			debug {
147 				assert ((ca.width == cb.width) && (cb.width == cc.width), "Width mismatch error!");
148 				assert ((ca.height == cb.height) && (cb.height == cc.height), "Height mismatch error!");
149 			}
150 			ObjectCollisionEvent event = new ObjectCollisionEvent(shA, shB, contextID, cc, ObjectCollisionEvent.Type.BoxOverlap);
151 			if(shA.shape !is null && shB.shape !is null) {
152 				for (int y ; y < cc.height ; y++) {
153 					for (int x ; x < cc.width ; x++) {
154 						if (shA.shape.readPixel(ca.left + x, ca.top + y) && shB.shape.readPixel(cb.left + x, cb.top + y)) {
155 							event.type = ObjectCollisionEvent.Type.ShapeOverlap;
156 							return event;
157 						}
158 					}
159 				}
160 			}
161 			return event;
162 		}
163 	}
164 	/+/**
165 	 * Tests two boxes together. Returns true on collision.
166 	 */
167 	protected bool areColliding(ref CollisionShape a, ref CollisionShape b){
168 		if (a.position.bottom < b.position.top) return false;
169 		if (a.position.top > b.position.bottom) return false;
170 		
171 		if (a.position.right < b.position.left) return false;
172 		if (a.position.left > b.position.right) return false;
173 
174 		Coordinate ca, cb, cc; // test area coordinates
175 		int cTPA, cTPB; // testpoints 
176 
177 		// process the test area and calculate the test points
178 		if(a.position.top >= b.position.top){
179 			cc.top = a.position.top;
180 			cTPA = a.position.width() * (a.position.top - b.position.top);
181 		}else{
182 			cc.top = b.position.top;
183 			cTPB = b.position.width() * (b.position.top - a.position.top);
184 		}
185 		if(a.position.bottom >= b.position.bottom){
186 			cc.bottom = b.position.bottom;
187 		}else{
188 			cc.bottom = a.position.bottom;
189 		}
190 		if(a.position.left >= b.position.left){
191 			cc.left = a.position.left;
192 			cTPA += a.position.left - b.position.left;
193 		}else{
194 			cc.left = b.position.left;
195 			cTPB += b.position.left - a.position.left;
196 		}
197 		if(a.position.right >= b.position.right){
198 			cc.right = b.position.right;
199 		}else{
200 			cc.right = a.position.right;
201 		}
202 		//writeln("A: x: ", ca.left," y: ", ca.top, "B: x: ", cb.left," y: ", cb.top, "C: x: ", cc.left," y: ", cc.top);
203 		for(int y ; y < cc.height ; y++) {
204 			for(int x ; x < cc.width ; x++) {
205 
206 			}
207 			cTPA += a.position.width();
208 			cTPB += b.position.width();
209 		}
210 
211 		return false;
212 	}+/
213 }