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  * Detects whether two objects have been collided, and if yes in what way. Capable of detecting:
17  * * Box overlap
18  * * Box edge
19  * * Shape overlap
20  * Note that shape overlap needs a custom collision shape for it to work, and cannot be mirrored in any way, instead
21  * "pre-mirroring" has to be done. Also sprite scaling isn't supported by this very collision detector.
22  */
23 public class ObjectCollisionDetector {
24 	ObjectMap			objects;	///Contains all of the objects 
25 	int					contextID;	///Stores the identifier of this detector
26 	/**
27 	 * The delegate where the events will be passed.
28 	 * Must be set up before using the collision detector.
29 	 */
30 	protected void delegate(ObjectCollisionEvent event)			objectToObjectCollision;
31 	/**
32 	 * Default CTOR that initializes the objectToObjectCollision, and contextID.
33 	 */
34 	public this (void delegate(ObjectCollisionEvent event) objectToObjectCollision, int contextID) @safe pure {
35 		assert(objectToObjectCollision !is null, "Delegate `objectToObjectCollision` must be a non-null value!");
36 		this.objectToObjectCollision = objectToObjectCollision;
37 		this.contextID = contextID;
38 	}
39 	/**
40 	 * Tests all shapes against each other
41 	 */
42 	public void testAll() {
43 		foreach (int iA, ref CollisionShape shA; objects) {
44 			testSingle(iA, &shA);
45 		}
46 	}
47 	/**
48 	 * Tests a single shape (objectID) against the others.
49 	 */
50 	public void testSingle(int objectID) {
51 		testSingle(objectID, objects.ptrOf(objectID));
52 	}
53 	///Tests a single shape against the others (internal).
54 	protected final void testSingle(int iA, CollisionShape* shA) {
55 		foreach (int iB, ref CollisionShape shB; objects) {
56 			if (iA != iB) {
57 				ObjectCollisionEvent event = testCollision(shA, &shB);
58 				if (event! is null) {
59 					event.idA = iA;
60 					event.idB = iB;
61 					objectToObjectCollision(event);
62 				}
63 			}
64 		}
65 	}
66 	/**
67 	 * Tests two objects. Calls cl if collision have happened, with the appropriate values.
68 	 */
69 	protected final ObjectCollisionEvent testCollision(CollisionShape* shA, CollisionShape* shB) pure {
70 		if (shA.position.bottom < shB.position.top || shA.position.top > shB.position.bottom || 
71 				shA.position.right < shB.position.left || shA.position.left > shB.position.right){
72 			//test if edge collision have happened with side edges
73 			if (shA.position.bottom >= shB.position.top && shA.position.top <= shB.position.bottom && 
74 					(shA.position.right - shB.position.left == -1 || shA.position.left - shB.position.right == 1)) {
75 				//calculate edge collision area
76 				Box cc;
77 				if(shA.position.top >= shB.position.top)
78 					cc.top = shA.position.top;
79 				else
80 					cc.top = shB.position.top;
81 				if(shA.position.bottom >= shB.position.bottom)
82 					cc.bottom = shB.position.bottom;
83 				else
84 					cc.bottom = shA.position.bottom;
85 				if (shA.position.right < shB.position.left) {
86 					cc.left = shA.position.right;
87 					cc.right = shB.position.left;
88 				} else {
89 					cc.left = shB.position.right;
90 					cc.right = shA.position.left;
91 				}
92 				return new ObjectCollisionEvent(shA, shB, contextID, cc, ObjectCollisionEvent.Type.BoxEdge);
93 			} else if (shA.position.right >= shB.position.left && shA.position.left <= shB.position.right && 
94 					(shA.position.bottom - shB.position.top == -1 || shA.position.top - shB.position.bottom == 1)) {
95 				//calculate edge collision area
96 				Box cc;
97 				if(shA.position.left >= shB.position.left)
98 					cc.left = shA.position.left;
99 				else
100 					cc.left = shB.position.left;
101 				if(shA.position.right >= shB.position.right)
102 					cc.right = shB.position.right;
103 				else
104 					cc.right = shA.position.right;
105 				if (shA.position.bottom < shB.position.top) {
106 					cc.top = shA.position.bottom;
107 					cc.bottom = shB.position.top;
108 				} else {
109 					cc.top = shB.position.bottom;
110 					cc.bottom = shA.position.top;
111 				}
112 				return new ObjectCollisionEvent(shA, shB, contextID, cc, ObjectCollisionEvent.Type.BoxEdge);
113 			} else return null;
114 		} else {
115 			//if there's a bitmap for both shapes, then proceed to per-pixel testing
116 			Box ca, cb, cc; // test area coordinates
117 			//ca: Shape a's overlap area
118 			//cb: Shape b's overlap area
119 			//cc: global overlap area
120 			//
121 			if (shA.position.top <= shB.position.top) {
122 				ca.top = shB.position.top - shA.position.top;
123 				cc.top = shB.position.top;
124 			} else {
125 				cb.top = shA.position.top - shB.position.top;
126 				cc.top = shA.position.top;
127 			}
128 			if(shA.position.bottom <= shB.position.bottom) {
129 				cc.bottom = shA.position.bottom;
130 				//cb.bottom = shB.position.bottom - shA.position.bottom;
131 				//ca.bottom = shB.position.height - 1;
132 			} else {
133 				cc.bottom = shB.position.bottom;
134 				//ca.bottom = shA.position.bottom - shB.position.bottom;
135 				//cb.bottom = shA.position.height - 1;
136 			}
137 			ca.bottom = ca.top + cc.height - 1;
138 			cb.bottom = cb.top + cc.height - 1;
139 			if (shA.position.left <= shB.position.left) {
140 				ca.left = shB.position.left - shA.position.left;
141 				cc.left = shB.position.left;
142 			} else {
143 				cb.left = shA.position.left - shB.position.left;
144 				cc.left = shA.position.left;
145 			}
146 			if (shA.position.right <= shB.position.right) {
147 				cc.right = shA.position.right;
148 				//cb.right = shB.position.right - shA.position.right;
149 				//ca.right = shB.position.width - 1;
150 			} else {
151 				cc.right = shB.position.right;
152 				//ca.right = shA.position.right - shB.position.right;
153 				//cb.right = shA.position.width - 1;
154 			}
155 			ca.right = ca.left + cc.width - 1;
156 			cb.right = cb.left + cc.width - 1;
157 			debug {
158 				assert ((ca.width == cb.width) && (cb.width == cc.width), "Width mismatch error!");
159 				assert ((ca.height == cb.height) && (cb.height == cc.height), "Height mismatch error!");
160 			}
161 			ObjectCollisionEvent event = new ObjectCollisionEvent(shA, shB, contextID, cc, ObjectCollisionEvent.Type.BoxOverlap);
162 			if(shA.shape !is null && shB.shape !is null) {
163 				/+for (int y ; y < cc.height ; y++) {
164 					for (int x ; x < cc.width ; x++) {
165 						if (shA.shape.readPixel(ca.left + x, ca.top + y) && shB.shape.readPixel(cb.left + x, cb.top + y)) {
166 							event.type = ObjectCollisionEvent.Type.ShapeOverlap;
167 							return event;
168 						}
169 					}
170 				}+/
171 				if (shA.position.left <= shB.position.left) {
172 					if (shA.shape.testCollision(ca.top, ca.height, shB.shape, cb.top, ca.left, cb.width))
173 						event.type = ObjectCollisionEvent.Type.ShapeOverlap;
174 				} else {
175 					if (shB.shape.testCollision(cb.top, cb.height, shA.shape, ca.top, cb.left, cb.width))
176 						event.type = ObjectCollisionEvent.Type.ShapeOverlap;
177 				}
178 			}
179 			return event;
180 		}
181 	}
182 	
183 }