1 /* 2 * Copyright (c) 2007-2013 Scott Lembcke and Howling Moon Software 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 */ 22 module dchip.cpPolyShape; 23 24 import dchip.cpBB; 25 import dchip.cpBody; 26 import dchip.chipmunk; 27 import dchip.chipmunk_private; 28 import dchip.chipmunk_types; 29 import dchip.cpShape; 30 import dchip.cpVect; 31 32 /// @private 33 struct cpSplittingPlane 34 { 35 cpVect n; 36 cpFloat d = 0; 37 } 38 39 /// @private 40 struct cpPolyShape 41 { 42 cpShape shape; 43 44 int numVerts; 45 cpVect* verts; 46 cpVect* tVerts; 47 cpSplittingPlane* planes; 48 cpSplittingPlane* tPlanes; 49 50 cpFloat r = 0; 51 } 52 53 cpPolyShape* cpPolyShapeAlloc() 54 { 55 return cast(cpPolyShape*)cpcalloc(1, cpPolyShape.sizeof); 56 } 57 58 cpBB cpPolyShapeTransformVerts(cpPolyShape* poly, cpVect p, cpVect rot) 59 { 60 cpVect* src = poly.verts; 61 cpVect* dst = poly.tVerts; 62 63 cpFloat l = cast(cpFloat)INFINITY, r = -cast(cpFloat)INFINITY; 64 cpFloat b = cast(cpFloat)INFINITY, t = -cast(cpFloat)INFINITY; 65 66 for (int i = 0; i < poly.numVerts; i++) 67 { 68 cpVect v = cpvadd(p, cpvrotate(src[i], rot)); 69 70 dst[i] = v; 71 l = cpfmin(l, v.x); 72 r = cpfmax(r, v.x); 73 b = cpfmin(b, v.y); 74 t = cpfmax(t, v.y); 75 } 76 77 cpFloat radius = poly.r; 78 return cpBBNew(l - radius, b - radius, r + radius, t + radius); 79 } 80 81 void cpPolyShapeTransformAxes(cpPolyShape* poly, cpVect p, cpVect rot) 82 { 83 cpSplittingPlane* src = poly.planes; 84 cpSplittingPlane* dst = poly.tPlanes; 85 86 for (int i = 0; i < poly.numVerts; i++) 87 { 88 cpVect n = cpvrotate(src[i].n, rot); 89 dst[i].n = n; 90 dst[i].d = cpvdot(p, n) + src[i].d; 91 } 92 } 93 94 cpBB cpPolyShapeCacheData(cpPolyShape* poly, cpVect p, cpVect rot) 95 { 96 cpPolyShapeTransformAxes(poly, p, rot); 97 cpBB bb = poly.shape.bb = cpPolyShapeTransformVerts(poly, p, rot); 98 99 return bb; 100 } 101 102 void cpPolyShapeDestroy(cpPolyShape* poly) 103 { 104 cpfree(poly.verts); 105 cpfree(poly.planes); 106 } 107 108 void cpPolyShapeNearestPointQuery(cpPolyShape* poly, cpVect p, cpNearestPointQueryInfo* info) 109 { 110 int count = poly.numVerts; 111 cpSplittingPlane* planes = poly.tPlanes; 112 cpVect* verts = poly.tVerts; 113 cpFloat r = poly.r; 114 115 cpVect v0 = verts[count - 1]; 116 cpFloat minDist = INFINITY; 117 cpVect closestPoint = cpvzero; 118 cpVect closestNormal = cpvzero; 119 cpBool outside = cpFalse; 120 121 for (int i = 0; i < count; i++) 122 { 123 if (cpSplittingPlaneCompare(planes[i], p) > 0.0f) 124 outside = cpTrue; 125 126 cpVect v1 = verts[i]; 127 cpVect closest = cpClosetPointOnSegment(p, v0, v1); 128 129 cpFloat dist = cpvdist(p, closest); 130 131 if (dist < minDist) 132 { 133 minDist = dist; 134 closestPoint = closest; 135 closestNormal = planes[i].n; 136 } 137 138 v0 = v1; 139 } 140 141 cpFloat dist = (outside ? minDist : -minDist); 142 cpVect g = cpvmult(cpvsub(p, closestPoint), 1.0f / dist); 143 144 info.shape = cast(cpShape*)poly; 145 info.p = cpvadd(closestPoint, cpvmult(g, r)); 146 info.d = dist - r; 147 148 // Use the normal of the closest segment if the distance is small. 149 info.g = (minDist > MAGIC_EPSILON ? g : closestNormal); 150 } 151 152 void cpPolyShapeSegmentQuery(cpPolyShape* poly, cpVect a, cpVect b, cpSegmentQueryInfo* info) 153 { 154 cpSplittingPlane* axes = poly.tPlanes; 155 cpVect* verts = poly.tVerts; 156 int numVerts = poly.numVerts; 157 cpFloat r = poly.r; 158 159 for (int i = 0; i < numVerts; i++) 160 { 161 cpVect n = axes[i].n; 162 cpFloat an = cpvdot(a, n); 163 cpFloat d = axes[i].d + r - an; 164 165 if (d > 0.0f) 166 continue; 167 168 cpFloat bn = cpvdot(b, n); 169 cpFloat t = d / (bn - an); 170 171 if (t < 0.0f || 1.0f < t) 172 continue; 173 174 cpVect point = cpvlerp(a, b, t); 175 cpFloat dt = -cpvcross(n, point); 176 cpFloat dtMin = -cpvcross(n, verts[(i - 1 + numVerts) % numVerts]); 177 cpFloat dtMax = -cpvcross(n, verts[i]); 178 179 if (dtMin <= dt && dt <= dtMax) 180 { 181 info.shape = cast(cpShape*)poly; 182 info.t = t; 183 info.n = n; 184 } 185 } 186 187 // Also check against the beveled vertexes. 188 if (r > 0.0f) 189 { 190 for (int i = 0; i < numVerts; i++) 191 { 192 cpSegmentQueryInfo circle_info = { null, 1.0f, cpvzero }; 193 CircleSegmentQuery(&poly.shape, verts[i], r, a, b, &circle_info); 194 195 if (circle_info.t < info.t) 196 (*info) = circle_info; 197 } 198 } 199 } 200 201 __gshared cpShapeClass polyClass; 202 203 void _initModuleCtor_cpPolyShape() 204 { 205 polyClass = cpShapeClass( 206 CP_POLY_SHAPE, 207 cast(cpShapeCacheDataImpl)&cpPolyShapeCacheData, 208 cast(cpShapeDestroyImpl)&cpPolyShapeDestroy, 209 cast(cpShapeNearestPointQueryImpl)&cpPolyShapeNearestPointQuery, 210 cast(cpShapeSegmentQueryImpl)&cpPolyShapeSegmentQuery, 211 ); 212 }; 213 214 cpBool cpPolyValidate(const cpVect* verts, const int numVerts) 215 { 216 for (int i = 0; i < numVerts; i++) 217 { 218 cpVect a = verts[i]; 219 cpVect b = verts[(i + 1) % numVerts]; 220 cpVect c = verts[(i + 2) % numVerts]; 221 222 if (cpvcross(cpvsub(b, a), cpvsub(c, a)) > 0.0f) 223 { 224 return cpFalse; 225 } 226 } 227 228 return cpTrue; 229 } 230 231 int cpPolyShapeGetNumVerts(const cpShape* shape) 232 { 233 cpAssertHard(shape.klass == &polyClass, "Shape is not a poly shape."); 234 return (cast(cpPolyShape*)shape).numVerts; 235 } 236 237 cpVect cpPolyShapeGetVert(const cpShape* shape, int idx) 238 { 239 cpAssertHard(shape.klass == &polyClass, "Shape is not a poly shape."); 240 cpAssertHard(0 <= idx && idx < cpPolyShapeGetNumVerts(shape), "Index out of range."); 241 242 return (cast(cpPolyShape*)shape).verts[idx]; 243 } 244 245 cpFloat cpPolyShapeGetRadius(const cpShape* shape) 246 { 247 cpAssertHard(shape.klass == &polyClass, "Shape is not a poly shape."); 248 return (cast(cpPolyShape*)shape).r; 249 } 250 251 void setUpVerts(cpPolyShape* poly, int numVerts, const cpVect* verts, cpVect offset) 252 { 253 // Fail if the user attempts to pass a concave poly, or a bad winding. 254 cpAssertHard(cpPolyValidate(verts, numVerts), "Polygon is concave or has a reversed winding. Consider using cpConvexHull()."); 255 256 poly.numVerts = numVerts; 257 poly.verts = cast(cpVect*)cpcalloc(2 * numVerts, cpVect.sizeof); 258 poly.planes = cast(cpSplittingPlane*)cpcalloc(2 * numVerts, cpSplittingPlane.sizeof); 259 poly.tVerts = poly.verts + numVerts; 260 poly.tPlanes = poly.planes + numVerts; 261 262 for (int i = 0; i < numVerts; i++) 263 { 264 cpVect a = cpvadd(offset, verts[i]); 265 cpVect b = cpvadd(offset, verts[(i + 1) % numVerts]); 266 cpVect n = cpvnormalize(cpvperp(cpvsub(b, a))); 267 268 poly.verts[i] = a; 269 poly.planes[i].n = n; 270 poly.planes[i].d = cpvdot(n, a); 271 } 272 273 // TODO: Why did I add this? It duplicates work from above. 274 for (int i = 0; i < numVerts; i++) 275 { 276 poly.planes[i] = cpSplittingPlaneNew(poly.verts[(i - 1 + numVerts) % numVerts], poly.verts[i]); 277 } 278 } 279 280 cpPolyShape* cpPolyShapeInit(cpPolyShape* poly, cpBody* body_, int numVerts, const cpVect* verts, cpVect offset) 281 { 282 return cpPolyShapeInit2(poly, body_, numVerts, verts, offset, 0.0f); 283 } 284 285 cpPolyShape* cpPolyShapeInit2(cpPolyShape* poly, cpBody* body_, int numVerts, const cpVect* verts, cpVect offset, cpFloat radius) 286 { 287 setUpVerts(poly, numVerts, verts, offset); 288 cpShapeInit(cast(cpShape*)poly, &polyClass, body_); 289 poly.r = radius; 290 291 return poly; 292 } 293 294 cpShape* cpPolyShapeNew(cpBody* body_, int numVerts, const cpVect* verts, cpVect offset) 295 { 296 return cpPolyShapeNew2(body_, numVerts, verts, offset, 0.0f); 297 } 298 299 cpShape* cpPolyShapeNew2(cpBody* body_, int numVerts, const cpVect* verts, cpVect offset, cpFloat radius) 300 { 301 return cast(cpShape*)cpPolyShapeInit2(cpPolyShapeAlloc(), body_, numVerts, verts, offset, radius); 302 } 303 304 cpPolyShape* cpBoxShapeInit(cpPolyShape* poly, cpBody* body_, cpFloat width, cpFloat height) 305 { 306 cpFloat hw = width / 2.0f; 307 cpFloat hh = height / 2.0f; 308 309 return cpBoxShapeInit2(poly, body_, cpBBNew(-hw, -hh, hw, hh)); 310 } 311 312 cpPolyShape* cpBoxShapeInit2(cpPolyShape* poly, cpBody* body_, cpBB box) 313 { 314 return cpBoxShapeInit3(poly, body_, box, 0.0f); 315 } 316 317 cpPolyShape* cpBoxShapeInit3(cpPolyShape* poly, cpBody* body_, cpBB box, cpFloat radius) 318 { 319 cpVect[4] verts = void; 320 verts[0] = cpv(box.l, box.b); 321 verts[1] = cpv(box.l, box.t); 322 verts[2] = cpv(box.r, box.t); 323 verts[3] = cpv(box.r, box.b); 324 325 return cpPolyShapeInit2(poly, body_, 4, verts.ptr, cpvzero, radius); 326 } 327 328 cpShape* cpBoxShapeNew(cpBody* body_, cpFloat width, cpFloat height) 329 { 330 return cast(cpShape*)cpBoxShapeInit(cpPolyShapeAlloc(), body_, width, height); 331 } 332 333 cpShape* cpBoxShapeNew2(cpBody* body_, cpBB box) 334 { 335 return cast(cpShape*)cpBoxShapeInit2(cpPolyShapeAlloc(), body_, box); 336 } 337 338 cpShape* cpBoxShapeNew3(cpBody* body_, cpBB box, cpFloat radius) 339 { 340 return cast(cpShape*)cpBoxShapeInit3(cpPolyShapeAlloc(), body_, box, radius); 341 } 342 343 // Unsafe API (chipmunk_unsafe.h) 344 345 void cpPolyShapeSetVerts(cpShape* shape, int numVerts, cpVect* verts, cpVect offset) 346 { 347 cpAssertHard(shape.klass == &polyClass, "Shape is not a poly shape."); 348 cpPolyShapeDestroy(cast(cpPolyShape*)shape); 349 setUpVerts(cast(cpPolyShape*)shape, numVerts, verts, offset); 350 } 351 352 void cpPolyShapeSetRadius(cpShape* shape, cpFloat radius) 353 { 354 cpAssertHard(shape.klass == &polyClass, "Shape is not a poly shape."); 355 (cast(cpPolyShape*)shape).r = radius; 356 }