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.chipmunk; 23 24 import core.stdc.stdlib : calloc, realloc, free; 25 import core.stdc..string : memcpy; 26 27 import std..string; 28 29 import dchip.cpBB; 30 import dchip.cpBody; 31 import dchip.cpPolyShape; 32 import dchip.chipmunk_types; 33 import dchip.cpVect; 34 import dchip.cpSpatialIndex; 35 import dchip.util; 36 37 /** Workaround for cycles between module constructors. */ 38 shared static this() 39 { 40 import dchip.cpBBTree; 41 import dchip.cpCollision; 42 import dchip.cpCollision; 43 import dchip.cpDampedRotarySpring; 44 import dchip.cpDampedSpring; 45 import dchip.cpGearJoint; 46 import dchip.cpGrooveJoint; 47 import dchip.cpPinJoint; 48 import dchip.cpPivotJoint; 49 import dchip.cpPolyShape; 50 import dchip.cpRatchetJoint; 51 import dchip.cpRotaryLimitJoint; 52 import dchip.cpShape; 53 import dchip.cpSimpleMotor; 54 import dchip.cpSlideJoint; 55 import dchip.cpSpaceHash; 56 import dchip.cpSweep1D; 57 58 _initModuleCtor_cpBBTree(); 59 _initModuleCtor_cpCollision(); 60 _initModuleCtor_cpDampedRotarySpring(); 61 _initModuleCtor_cpDampedSpring(); 62 _initModuleCtor_cpGearJoint(); 63 _initModuleCtor_cpGrooveJoint(); 64 _initModuleCtor_cpPinJoint(); 65 _initModuleCtor_cpPivotJoint(); 66 _initModuleCtor_cpPolyShape(); 67 _initModuleCtor_cpRatchetJoint(); 68 _initModuleCtor_cpRotaryLimitJoint(); 69 _initModuleCtor_cpShape(); 70 _initModuleCtor_cpSimpleMotor(); 71 _initModuleCtor_cpSlideJoint(); 72 _initModuleCtor_cpSpaceHash(); 73 _initModuleCtor_cpSweep1D(); 74 } 75 76 /** 77 Workaround for a linker bug with RDMD local imports: 78 http://d.puremagic.com/issues/show_bug.cgi?id=7016 79 */ 80 version (CHIP_ENABLE_UNITTESTS) 81 { 82 import dchip.util; 83 } 84 85 /** 86 Throw when an internal library condition is unsatisfied. 87 Continuing the use of the library after this error is 88 thrown will lead to undefined behavior. 89 */ 90 class DChipError : Error 91 { 92 /// 93 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) 94 { 95 super(msg, file, line, next); 96 } 97 } 98 99 /** 100 If the $(D CHIP_ENABLE_WARNINGS) version is set, 101 print a warning to stderr if condition is false. 102 */ 103 void cpAssertWarn(string file = __FILE__, size_t line = __LINE__, E, Args...) 104 (lazy E condition, lazy string expr, lazy Args args) 105 { 106 version (CHIP_ENABLE_WARNINGS) 107 { 108 import std.stdio : stderr, writefln; 109 110 if (!!condition) 111 return; 112 113 static if (Args.length) 114 { 115 static if (Args.length > 1) 116 { 117 import std..string : format; 118 string msg = format(args[0], args[1 .. $]); 119 } 120 else 121 { 122 import std.conv : text; 123 string msg = text(args); 124 } 125 } 126 else 127 enum msg = "Requirement failed"; 128 129 stderr.writefln(`%s(%s): Warning: %s. Failed condition: "%s".`, file, line, msg, expr); 130 } 131 } 132 133 /// 134 version (CHIP_ENABLE_UNITTESTS) 135 unittest 136 { 137 int iteration = 10; 138 int WARN_GJK_ITERATIONS = 10; 139 140 static assert (is(typeof({ 141 cpAssertWarn(iteration < WARN_GJK_ITERATIONS, 142 "iteration < WARN_GJK_ITERATIONS"); 143 144 cpAssertWarn(iteration < WARN_GJK_ITERATIONS, 145 "iteration < WARN_GJK_ITERATIONS", 146 "High GJK iterations: %d", iteration); 147 }()))); 148 } 149 150 /** 151 If the $(D CHIP_ENABLE_WARNINGS) version is set, 152 throw a $(D DChipError) if condition is false. 153 */ 154 void cpAssertSoft(string file = __FILE__, size_t line = __LINE__, E, Args...) 155 (lazy E condition, lazy string expr, lazy Args args) 156 { 157 version (CHIP_ENABLE_WARNINGS) 158 { 159 cpAssertHard!(file, line, E, Args)(condition, expr, args); 160 } 161 } 162 163 /// 164 version (CHIP_ENABLE_UNITTESTS) 165 unittest 166 { 167 import std.exception : assertNotThrown; 168 import dchip.util : assertErrorsWith; 169 170 int iteration = 10; 171 int WARN_GJK_ITERATIONS = 10; 172 173 version (CHIP_ENABLE_WARNINGS) 174 { 175 cpAssertSoft(iteration < WARN_GJK_ITERATIONS, "iteration < WARN_GJK_ITERATIONS") 176 .assertErrorsWith(`Error: Requirement failed. Failed condition: "iteration < WARN_GJK_ITERATIONS".`); 177 178 assertNotThrown!DChipError(cpAssertSoft(iteration == WARN_GJK_ITERATIONS, 179 "iteration == WARN_GJK_ITERATIONS")); 180 } 181 else 182 { 183 assertNotThrown!DChipError(cpAssertSoft(iteration < WARN_GJK_ITERATIONS, 184 "iteration < WARN_GJK_ITERATIONS")); 185 } 186 } 187 188 /** Throw a $(D DChipError) if condition is false. */ 189 void cpAssertHard(string file = __FILE__, size_t line = __LINE__, E, Args...) 190 (lazy E condition, lazy string expr, lazy Args args) 191 { 192 import std..string : format; 193 194 if (!!condition) 195 return; 196 197 static if (Args.length) 198 { 199 static if (Args.length > 1) 200 { 201 string msg = format(args[0], args[1 .. $]); 202 } 203 else 204 { 205 import std.conv : text; 206 string msg = text(args); 207 } 208 } 209 else 210 enum msg = "Requirement failed"; 211 212 throw new DChipError(format(`Error: %s. Failed condition: "%s".`, msg, expr), file, line); 213 } 214 215 /// 216 version (CHIP_ENABLE_UNITTESTS) 217 unittest 218 { 219 import std.exception : assertNotThrown; 220 import dchip.util : assertErrorsWith; 221 222 int iteration = 10; 223 int WARN_GJK_ITERATIONS = 10; 224 225 cpAssertHard(iteration < WARN_GJK_ITERATIONS, "iteration < WARN_GJK_ITERATIONS") 226 .assertErrorsWith(`Error: Requirement failed. Failed condition: "iteration < WARN_GJK_ITERATIONS".`); 227 228 assertNotThrown!DChipError(cpAssertHard(iteration == WARN_GJK_ITERATIONS, 229 "iteration == WARN_GJK_ITERATIONS")); 230 } 231 232 /// Allocated size for various Chipmunk buffers. 233 enum CP_BUFFER_BYTES = 32 * 1024; 234 235 /// Chipmunk calloc() alias. 236 alias cpcalloc = calloc; 237 238 /// Chipmunk realloc() alias. 239 alias cprealloc = realloc; 240 241 /// Chipmunk free() alias. 242 alias cpfree = free; 243 244 /// Chipmunk 6.2.1 245 enum CP_VERSION_MAJOR = 6; 246 enum CP_VERSION_MINOR = 2; 247 enum CP_VERSION_RELEASE = 1; 248 249 /// Version string. 250 enum cpVersionString = format("%s.%s.%s", CP_VERSION_MAJOR, CP_VERSION_MINOR, CP_VERSION_RELEASE); 251 252 /// @deprecated 253 deprecated("cpInitChipmunk is deprecated and no longer required. It will be removed in the future.") 254 void cpInitChipmunk() { } 255 256 /// Calculate the moment of inertia for a circle. 257 /// @c r1 and @c r2 are the inner and outer diameters. A solid circle has an inner diameter of 0. 258 cpFloat cpMomentForCircle(cpFloat m, cpFloat r1, cpFloat r2, cpVect offset) 259 { 260 return m * (0.5f * (r1 * r1 + r2 * r2) + cpvlengthsq(offset)); 261 } 262 263 /// Calculate area of a hollow circle. 264 /// @c r1 and @c r2 are the inner and outer diameters. A solid circle has an inner diameter of 0. 265 cpFloat cpAreaForCircle(cpFloat r1, cpFloat r2) 266 { 267 return safeCast!cpFloat(M_PI) * cpfabs(r1 * r1 - r2 * r2); 268 } 269 270 /// Calculate the moment of inertia for a line segment. 271 /// Beveling radius is not supported. 272 cpFloat cpMomentForSegment(cpFloat m, cpVect a, cpVect b) 273 { 274 cpVect offset = cpvmult(cpvadd(a, b), 0.5f); 275 return m * (cpvdistsq(b, a) / 12.0f + cpvlengthsq(offset)); 276 } 277 278 /// Calculate the area of a fattened (capsule shaped) line segment. 279 cpFloat cpAreaForSegment(cpVect a, cpVect b, cpFloat r) 280 { 281 return r * (safeCast!cpFloat(M_PI) * r + 2.0f * cpvdist(a, b)); 282 } 283 284 /// Calculate the moment of inertia for a solid polygon shape assuming it's center of gravity is at it's centroid. The offset is added to each vertex. 285 cpFloat cpMomentForPoly(cpFloat m, const int numVerts, const cpVect* verts, cpVect offset) 286 { 287 if (numVerts == 2) 288 return cpMomentForSegment(m, verts[0], verts[1]); 289 290 cpFloat sum1 = 0.0f; 291 cpFloat sum2 = 0.0f; 292 293 for (int i = 0; i < numVerts; i++) 294 { 295 cpVect v1 = cpvadd(verts[i], offset); 296 cpVect v2 = cpvadd(verts[(i + 1) % numVerts], offset); 297 298 cpFloat a = cpvcross(v2, v1); 299 cpFloat b = cpvdot(v1, v1) + cpvdot(v1, v2) + cpvdot(v2, v2); 300 301 sum1 += a * b; 302 sum2 += a; 303 } 304 305 return (m * sum1) / (6.0f * sum2); 306 } 307 308 /// Calculate the signed area of a polygon. A Clockwise winding gives positive area. 309 /// This is probably backwards from what you expect, but matches Chipmunk's the winding for poly shapes. 310 cpFloat cpAreaForPoly(const int numVerts, const cpVect* verts) 311 { 312 cpFloat area = 0.0f; 313 314 for (int i = 0; i < numVerts; i++) 315 { 316 area += cpvcross(verts[i], verts[(i + 1) % numVerts]); 317 } 318 319 return -area / 2.0f; 320 } 321 322 void cpLoopIndexes(cpVect* verts, int count, int* start, int* end) 323 { 324 (*start) = (*end) = 0; 325 cpVect min = verts[0]; 326 cpVect max = min; 327 328 for (int i = 1; i < count; i++) 329 { 330 cpVect v = verts[i]; 331 332 if (v.x < min.x || (v.x == min.x && v.y < min.y)) 333 { 334 min = v; 335 (*start) = i; 336 } 337 else if (v.x > max.x || (v.x == max.x && v.y > max.y)) 338 { 339 max = v; 340 (*end) = i; 341 } 342 } 343 } 344 345 /** Avoid bringing in std.algorithm. */ 346 void SWAP(ref cpVect a, ref cpVect b) 347 { 348 cpVect tmp = a; 349 a = b; 350 b = tmp; 351 } 352 353 int QHullPartition(cpVect* verts, int count, cpVect a, cpVect b, cpFloat tol) 354 { 355 if (count == 0) 356 return 0; 357 358 cpFloat max = 0; 359 int pivot = 0; 360 361 cpVect delta = cpvsub(b, a); 362 cpFloat valueTol = tol * cpvlength(delta); 363 364 int head = 0; 365 366 for (int tail = count - 1; head <= tail; ) 367 { 368 cpFloat value = cpvcross(delta, cpvsub(verts[head], a)); 369 370 if (value > valueTol) 371 { 372 if (value > max) 373 { 374 max = value; 375 pivot = head; 376 } 377 378 head++; 379 } 380 else 381 { 382 SWAP(verts[head], verts[tail]); 383 tail--; 384 } 385 } 386 387 // move the new pivot to the front if it's not already there. 388 if (pivot != 0) 389 SWAP(verts[0], verts[pivot]); 390 return head; 391 } 392 393 int QHullReduce(cpFloat tol, cpVect* verts, int count, cpVect a, cpVect pivot, cpVect b, cpVect* result) 394 { 395 if (count < 0) 396 { 397 return 0; 398 } 399 else if (count == 0) 400 { 401 result[0] = pivot; 402 return 1; 403 } 404 else 405 { 406 int left_count = QHullPartition(verts, count, a, pivot, tol); 407 int index = QHullReduce(tol, verts + 1, left_count - 1, a, verts[0], pivot, result); 408 409 result[index++] = pivot; 410 411 int right_count = QHullPartition(verts + left_count, count - left_count, pivot, b, tol); 412 return index + QHullReduce(tol, verts + left_count + 1, right_count - 1, pivot, verts[left_count], b, result + index); 413 } 414 } 415 416 /// Calculate the natural centroid of a polygon. 417 cpVect cpCentroidForPoly(const int numVerts, const cpVect* verts) 418 { 419 cpFloat sum = 0.0f; 420 cpVect vsum = cpvzero; 421 422 for (int i = 0; i < numVerts; i++) 423 { 424 cpVect v1 = verts[i]; 425 cpVect v2 = verts[(i + 1) % numVerts]; 426 cpFloat cross = cpvcross(v1, v2); 427 428 sum += cross; 429 vsum = cpvadd(vsum, cpvmult(cpvadd(v1, v2), cross)); 430 } 431 432 return cpvmult(vsum, 1.0f / (3.0f * sum)); 433 } 434 435 /// Center the polygon on the origin. (Subtracts the centroid of the polygon from each vertex) 436 void cpRecenterPoly(const int numVerts, cpVect* verts) 437 { 438 cpVect centroid = cpCentroidForPoly(numVerts, verts); 439 440 for (int i = 0; i < numVerts; i++) 441 { 442 verts[i] = cpvsub(verts[i], centroid); 443 } 444 } 445 446 /// Calculate the moment of inertia for a solid box. 447 cpFloat cpMomentForBox(cpFloat m, cpFloat width, cpFloat height) 448 { 449 return m * (width * width + height * height) / 12.0f; 450 } 451 452 /// Calculate the moment of inertia for a solid box. 453 cpFloat cpMomentForBox2(cpFloat m, cpBB box) 454 { 455 cpFloat width = box.r - box.l; 456 cpFloat height = box.t - box.b; 457 cpVect offset = cpvmult(cpv(box.l + box.r, box.b + box.t), 0.5f); 458 459 // TODO NaN when offset is 0 and m is INFINITY 460 return cpMomentForBox(m, width, height) + m * cpvlengthsq(offset); 461 } 462 463 /// Calculate the convex hull of a given set of points. Returns the count of points in the hull. 464 /// @c result must be a pointer to a @c cpVect array with at least @c count elements. If @c result is @c NULL, then @c verts will be reduced instead. 465 /// @c first is an optional pointer to an integer to store where the first vertex in the hull came from (i.e. verts[first] == result[0]) 466 /// @c tol is the allowed amount to shrink the hull when simplifying it. A tolerance of 0.0 creates an exact hull. 467 /// QuickHull seemed like a neat algorithm, and efficient-ish for large input sets. 468 /// My implementation performs an in place reduction using the result array as scratch space. 469 int cpConvexHull(int count, cpVect* verts, cpVect* result, int* first, cpFloat tol) 470 { 471 if (result) 472 { 473 // Copy the line vertexes into the empty part of the result polyline to use as a scratch buffer. 474 memcpy(result, verts, count * cpVect.sizeof); 475 } 476 else 477 { 478 // If a result array was not specified, reduce the input instead. 479 result = verts; 480 } 481 482 // Degenerate case, all poins are the same. 483 int start, end; 484 cpLoopIndexes(verts, count, &start, &end); 485 486 if (start == end) 487 { 488 if (first) 489 (*first) = 0; 490 return 1; 491 } 492 493 SWAP(result[0], result[start]); 494 SWAP(result[1], result[end == 0 ? start : end]); 495 496 cpVect a = result[0]; 497 cpVect b = result[1]; 498 499 if (first) 500 (*first) = start; 501 int resultCount = QHullReduce(tol, result + 2, count - 2, a, b, a, result + 1) + 1; 502 cpAssertSoft(cpPolyValidate(result, resultCount), 503 "Internal error: cpConvexHull() and cpPolyValidate() did not agree."~ 504 "Please report this error with as much info as you can."); 505 return resultCount; 506 }