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 ///Ditto 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 * Tests two boxes together. Returns true on collision. 184 */ 185 protected bool areColliding(ref CollisionShape a, ref CollisionShape b){ 186 if (a.position.bottom < b.position.top) return false; 187 if (a.position.top > b.position.bottom) return false; 188 189 if (a.position.right < b.position.left) return false; 190 if (a.position.left > b.position.right) return false; 191 192 Coordinate ca, cb, cc; // test area coordinates 193 int cTPA, cTPB; // testpoints 194 195 // process the test area and calculate the test points 196 if(a.position.top >= b.position.top){ 197 cc.top = a.position.top; 198 cTPA = a.position.width() * (a.position.top - b.position.top); 199 }else{ 200 cc.top = b.position.top; 201 cTPB = b.position.width() * (b.position.top - a.position.top); 202 } 203 if(a.position.bottom >= b.position.bottom){ 204 cc.bottom = b.position.bottom; 205 }else{ 206 cc.bottom = a.position.bottom; 207 } 208 if(a.position.left >= b.position.left){ 209 cc.left = a.position.left; 210 cTPA += a.position.left - b.position.left; 211 }else{ 212 cc.left = b.position.left; 213 cTPB += b.position.left - a.position.left; 214 } 215 if(a.position.right >= b.position.right){ 216 cc.right = b.position.right; 217 }else{ 218 cc.right = a.position.right; 219 } 220 //writeln("A: x: ", ca.left," y: ", ca.top, "B: x: ", cb.left," y: ", cb.top, "C: x: ", cc.left," y: ", cc.top); 221 for(int y ; y < cc.height ; y++) { 222 for(int x ; x < cc.width ; x++) { 223 224 } 225 cTPA += a.position.width(); 226 cTPB += b.position.width(); 227 } 228 229 return false; 230 }+/ 231 }