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 }