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 demo.Joints;
23 
24 import core.stdc.stdlib;
25 
26 import std.math;
27 
28 alias M_PI_2 = PI_2;
29 
30 import demo.dchip;
31 
32 import demo.ChipmunkDebugDraw;
33 import demo.ChipmunkDemo;
34 import demo.types;
35 
36 static cpBody* addBall(cpSpace* space, cpVect pos, cpVect boxOffset)
37 {
38     cpFloat radius = 15.0f;
39     cpFloat mass   = 1.0f;
40     cpBody* body_   = cpSpaceAddBody(space, cpBodyNew(mass, cpMomentForCircle(mass, 0.0f, radius, cpvzero)));
41     cpBodySetPos(body_, cpvadd(pos, boxOffset));
42 
43     cpShape* shape = cpSpaceAddShape(space, cpCircleShapeNew(body_, radius, cpvzero));
44     cpShapeSetElasticity(shape, 0.0f);
45     cpShapeSetFriction(shape, 0.7f);
46 
47     return body_;
48 }
49 
50 static cpBody* addLever(cpSpace* space, cpVect pos, cpVect boxOffset)
51 {
52     cpFloat mass = 1.0f;
53     cpVect  a    = cpv(0, 15);
54     cpVect  b    = cpv(0, -15);
55 
56     cpBody* body_ = cpSpaceAddBody(space, cpBodyNew(mass, cpMomentForSegment(mass, a, b)));
57     cpBodySetPos(body_, cpvadd(pos, cpvadd(boxOffset, cpv(0, -15))));
58 
59     cpShape* shape = cpSpaceAddShape(space, cpSegmentShapeNew(body_, a, b, 5.0f));
60     cpShapeSetElasticity(shape, 0.0f);
61     cpShapeSetFriction(shape, 0.7f);
62 
63     return body_;
64 }
65 
66 static cpBody* addBar(cpSpace* space, cpVect pos, cpVect boxOffset)
67 {
68     cpFloat mass = 2.0f;
69     cpVect  a    = cpv(0, 30);
70     cpVect  b    = cpv(0, -30);
71 
72     cpBody* body_ = cpSpaceAddBody(space, cpBodyNew(mass, cpMomentForSegment(mass, a, b)));
73     cpBodySetPos(body_, cpvadd(pos, boxOffset));
74 
75     cpShape* shape = cpSpaceAddShape(space, cpSegmentShapeNew(body_, a, b, 5.0f));
76     cpShapeSetElasticity(shape, 0.0f);
77     cpShapeSetFriction(shape, 0.7f);
78     cpShapeSetGroup(shape, 1);
79 
80     return body_;
81 }
82 
83 static cpBody* addWheel(cpSpace* space, cpVect pos, cpVect boxOffset)
84 {
85     cpFloat radius = 15.0f;
86     cpFloat mass   = 1.0f;
87     cpBody* body_   = cpSpaceAddBody(space, cpBodyNew(mass, cpMomentForCircle(mass, 0.0f, radius, cpvzero)));
88     cpBodySetPos(body_, cpvadd(pos, boxOffset));
89 
90     cpShape* shape = cpSpaceAddShape(space, cpCircleShapeNew(body_, radius, cpvzero));
91     cpShapeSetElasticity(shape, 0.0f);
92     cpShapeSetFriction(shape, 0.7f);
93     cpShapeSetGroup(shape, 1);     // use a group to keep the car parts from colliding
94 
95     return body_;
96 }
97 
98 static cpBody* addChassis(cpSpace* space, cpVect pos, cpVect boxOffset)
99 {
100     cpFloat mass   = 5.0f;
101     cpFloat width  = 80;
102     cpFloat height = 30;
103 
104     cpBody* body_ = cpSpaceAddBody(space, cpBodyNew(mass, cpMomentForBox(mass, width, height)));
105     cpBodySetPos(body_, cpvadd(pos, boxOffset));
106 
107     cpShape* shape = cpSpaceAddShape(space, cpBoxShapeNew(body_, width, height));
108     cpShapeSetElasticity(shape, 0.0f);
109     cpShapeSetFriction(shape, 0.7f);
110     cpShapeSetGroup(shape, 1);     // use a group to keep the car parts from colliding
111 
112     return body_;
113 }
114 
115 static cpSpace* init()
116 {
117     cpSpace* space = cpSpaceNew();
118     cpSpaceSetIterations(space, 10);
119     cpSpaceSetGravity(space, cpv(0, -100));
120     cpSpaceSetSleepTimeThreshold(space, 0.5f);
121 
122     cpBody * staticBody = cpSpaceGetStaticBody(space);
123     cpShape* shape;
124 
125     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, 240), cpv(320, 240), 0.0f));
126     cpShapeSetElasticity(shape, 1.0f);
127     cpShapeSetFriction(shape, 1.0f);
128     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
129 
130     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, 120), cpv(320, 120), 0.0f));
131     cpShapeSetElasticity(shape, 1.0f);
132     cpShapeSetFriction(shape, 1.0f);
133     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
134 
135     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, 0), cpv(320, 0), 0.0f));
136     cpShapeSetElasticity(shape, 1.0f);
137     cpShapeSetFriction(shape, 1.0f);
138     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
139 
140     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, -120), cpv(320, -120), 0.0f));
141     cpShapeSetElasticity(shape, 1.0f);
142     cpShapeSetFriction(shape, 1.0f);
143     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
144 
145     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, -240), cpv(320, -240), 0.0f));
146     cpShapeSetElasticity(shape, 1.0f);
147     cpShapeSetFriction(shape, 1.0f);
148     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
149 
150     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, -240), cpv(-320, 240), 0.0f));
151     cpShapeSetElasticity(shape, 1.0f);
152     cpShapeSetFriction(shape, 1.0f);
153     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
154 
155     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-160, -240), cpv(-160, 240), 0.0f));
156     cpShapeSetElasticity(shape, 1.0f);
157     cpShapeSetFriction(shape, 1.0f);
158     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
159 
160     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(0, -240), cpv(0, 240), 0.0f));
161     cpShapeSetElasticity(shape, 1.0f);
162     cpShapeSetFriction(shape, 1.0f);
163     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
164 
165     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(160, -240), cpv(160, 240), 0.0f));
166     cpShapeSetElasticity(shape, 1.0f);
167     cpShapeSetFriction(shape, 1.0f);
168     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
169 
170     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(320, -240), cpv(320, 240), 0.0f));
171     cpShapeSetElasticity(shape, 1.0f);
172     cpShapeSetFriction(shape, 1.0f);
173     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
174 
175     cpVect  boxOffset;
176     cpBody* body1;
177     cpBody* body2;
178 
179     cpVect posA = cpv(50, 60);
180     cpVect posB = cpv(110, 60);
181 
182     auto POS_A() { return cpvadd(boxOffset, posA); }
183     auto POS_B() { return cpvadd(boxOffset, posB); }
184 
185     // Pin Joints - Link shapes with a solid bar or pin.
186     // Keeps the anchor points the same distance apart from when the joint was created.
187     boxOffset = cpv(-320, -240);
188     body1     = addBall(space, posA, boxOffset);
189     body2     = addBall(space, posB, boxOffset);
190     cpSpaceAddConstraint(space, cpPinJointNew(body1, body2, cpv(15, 0), cpv(-15, 0)));
191 
192     // Slide Joints - Like pin joints but with a min/max distance.
193     // Can be used for a cheap approximation of a rope.
194     boxOffset = cpv(-160, -240);
195     body1     = addBall(space, posA, boxOffset);
196     body2     = addBall(space, posB, boxOffset);
197     cpSpaceAddConstraint(space, cpSlideJointNew(body1, body2, cpv(15, 0), cpv(-15, 0), 20.0f, 40.0f));
198 
199     // Pivot Joints - Holds the two anchor points together. Like a swivel.
200     boxOffset = cpv(0, -240);
201     body1     = addBall(space, posA, boxOffset);
202     body2     = addBall(space, posB, boxOffset);
203     cpSpaceAddConstraint(space, cpPivotJointNew(body1, body2, cpvadd(boxOffset, cpv(80, 60))));
204 
205     // cpPivotJointNew() takes it's anchor parameter in world coordinates. The anchors are calculated from that
206     // cpPivotJointNew2() lets you specify the two anchor points explicitly
207 
208     // Groove Joints - Like a pivot joint, but one of the anchors is a line segment that the pivot can slide in
209     boxOffset = cpv(160, -240);
210     body1     = addBall(space, posA, boxOffset);
211     body2     = addBall(space, posB, boxOffset);
212     cpSpaceAddConstraint(space, cpGrooveJointNew(body1, body2, cpv(30, 30), cpv(30, -30), cpv(-30, 0)));
213 
214     // Damped Springs
215     boxOffset = cpv(-320, -120);
216     body1     = addBall(space, posA, boxOffset);
217     body2     = addBall(space, posB, boxOffset);
218     cpSpaceAddConstraint(space, cpDampedSpringNew(body1, body2, cpv(15, 0), cpv(-15, 0), 20.0f, 5.0f, 0.3f));
219 
220     // Damped Rotary Springs
221     boxOffset = cpv(-160, -120);
222     body1     = addBar(space, posA, boxOffset);
223     body2     = addBar(space, posB, boxOffset);
224 
225     // Add some pin joints to hold the circles in place.
226     cpSpaceAddConstraint(space, cpPivotJointNew(body1, staticBody, POS_A));
227     cpSpaceAddConstraint(space, cpPivotJointNew(body2, staticBody, POS_B));
228     cpSpaceAddConstraint(space, cpDampedRotarySpringNew(body1, body2, 0.0f, 3000.0f, 60.0f));
229 
230     // Rotary Limit Joint
231     boxOffset = cpv(0, -120);
232     body1     = addLever(space, posA, boxOffset);
233     body2     = addLever(space, posB, boxOffset);
234 
235     // Add some pin joints to hold the circles in place.
236     cpSpaceAddConstraint(space, cpPivotJointNew(body1, staticBody, POS_A));
237     cpSpaceAddConstraint(space, cpPivotJointNew(body2, staticBody, POS_B));
238 
239     // Hold their rotation within 90 degrees of each other.
240     cpSpaceAddConstraint(space, cpRotaryLimitJointNew(body1, body2, -M_PI_2, M_PI_2));
241 
242     // Ratchet Joint - A rotary ratchet, like a socket wrench
243     boxOffset = cpv(160, -120);
244     body1     = addLever(space, posA, boxOffset);
245     body2     = addLever(space, posB, boxOffset);
246 
247     // Add some pin joints to hold the circles in place.
248     cpSpaceAddConstraint(space, cpPivotJointNew(body1, staticBody, POS_A));
249     cpSpaceAddConstraint(space, cpPivotJointNew(body2, staticBody, POS_B));
250 
251     // Ratchet every 90 degrees
252     cpSpaceAddConstraint(space, cpRatchetJointNew(body1, body2, 0.0f, M_PI_2));
253 
254     // Gear Joint - Maintain a specific angular velocity ratio
255     boxOffset = cpv(-320, 0);
256     body1     = addBar(space, posA, boxOffset);
257     body2     = addBar(space, posB, boxOffset);
258 
259     // Add some pin joints to hold the circles in place.
260     cpSpaceAddConstraint(space, cpPivotJointNew(body1, staticBody, POS_A));
261     cpSpaceAddConstraint(space, cpPivotJointNew(body2, staticBody, POS_B));
262 
263     // Force one to sping 2x as fast as the other
264     cpSpaceAddConstraint(space, cpGearJointNew(body1, body2, 0.0f, 2.0f));
265 
266     // Simple Motor - Maintain a specific angular relative velocity
267     boxOffset = cpv(-160, 0);
268     body1     = addBar(space, posA, boxOffset);
269     body2     = addBar(space, posB, boxOffset);
270 
271     // Add some pin joints to hold the circles in place.
272     cpSpaceAddConstraint(space, cpPivotJointNew(body1, staticBody, POS_A));
273     cpSpaceAddConstraint(space, cpPivotJointNew(body2, staticBody, POS_B));
274 
275     // Make them spin at 1/2 revolution per second in relation to each other.
276     cpSpaceAddConstraint(space, cpSimpleMotorNew(body1, body2, M_PI));
277 
278     // Make a car with some nice soft suspension
279     boxOffset = cpv(0, 0);
280     cpBody* wheel1  = addWheel(space, posA, boxOffset);
281     cpBody* wheel2  = addWheel(space, posB, boxOffset);
282     cpBody* chassis = addChassis(space, cpv(80, 100), boxOffset);
283 
284     cpSpaceAddConstraint(space, cpGrooveJointNew(chassis, wheel1, cpv(-30, -10), cpv(-30, -40), cpvzero));
285     cpSpaceAddConstraint(space, cpGrooveJointNew(chassis, wheel2, cpv(30, -10), cpv(30, -40), cpvzero));
286 
287     cpSpaceAddConstraint(space, cpDampedSpringNew(chassis, wheel1, cpv(-30, 0), cpvzero, 50.0f, 20.0f, 10.0f));
288     cpSpaceAddConstraint(space, cpDampedSpringNew(chassis, wheel2, cpv(30, 0), cpvzero, 50.0f, 20.0f, 10.0f));
289 
290     return space;
291 }
292 
293 static void update(cpSpace* space, double dt)
294 {
295     cpSpaceStep(space, dt);
296 }
297 
298 static void destroy(cpSpace* space)
299 {
300     ChipmunkDemoFreeSpaceChildren(space);
301     cpSpaceFree(space);
302 }
303 
304 ChipmunkDemo Joints = {
305     "Joints and Constraints",
306     1.0 / 60.0,
307     &init,
308     &update,
309     &ChipmunkDemoDefaultDrawImpl,
310     &destroy,
311 };