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 }