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.Buoyancy;
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 void update(cpSpace* space, double dt)
37 {
38     cpSpaceStep(space, dt);
39 }
40 
41 enum FLUID_DENSITY = 0.00014;
42 enum FLUID_DRAG = 2.0;
43 
44 char messageBuffer[1024] = 0;
45 
46 static cpBool waterPreSolve(cpArbiter* arb, cpSpace* space, void* ptr)
47 {
48     mixin(CP_ARBITER_GET_SHAPES!("arb", "water", "poly"));
49     cpBody* body_ = cpShapeGetBody(poly);
50 
51     // Get the top of the water sensor bounding box to use as the water level.
52     cpFloat level = cpShapeGetBB(water).t;
53 
54     // Clip the polygon against the water level
55     int count        = cpPolyShapeGetNumVerts(poly);
56     int clippedCount = 0;
57 
58     cpVect[10] clipped;
59 
60     for (int i = 0, j = count - 1; i < count; j = i, i++)
61     {
62         cpVect a = cpBodyLocal2World(body_, cpPolyShapeGetVert(poly, j));
63         cpVect b = cpBodyLocal2World(body_, cpPolyShapeGetVert(poly, i));
64 
65         if (a.y < level)
66         {
67             clipped[clippedCount] = a;
68             clippedCount++;
69         }
70 
71         cpFloat a_level = a.y - level;
72         cpFloat b_level = b.y - level;
73 
74         if (a_level * b_level < 0.0f)
75         {
76             cpFloat t = cpfabs(a_level) / (cpfabs(a_level) + cpfabs(b_level));
77 
78             clipped[clippedCount] = cpvlerp(a, b, t);
79             clippedCount++;
80         }
81     }
82 
83     // Calculate buoyancy from the clipped polygon area
84     cpFloat clippedArea   = cpAreaForPoly(clippedCount, clipped.ptr);
85     cpFloat displacedMass = clippedArea * FLUID_DENSITY;
86     cpVect  centroid      = cpCentroidForPoly(clippedCount, clipped.ptr);
87     cpVect  r = cpvsub(centroid, cpBodyGetPos(body_));
88 
89     ChipmunkDebugDrawPolygon(clippedCount, clipped.ptr, 0.0f, RGBAColor(0, 0, 1, 1), RGBAColor(0, 0, 1, 0.1f));
90     ChipmunkDebugDrawDot(5, centroid, RGBAColor(0, 0, 1, 1));
91 
92     cpFloat dt = cpSpaceGetCurrentTimeStep(space);
93     cpVect  g  = cpSpaceGetGravity(space);
94 
95     // Apply the buoyancy force as an impulse.
96     apply_impulse(body_, cpvmult(g, -displacedMass * dt), r);
97 
98     // Apply linear damping for the fluid drag.
99     cpVect  v_centroid = cpvadd(body_.v, cpvmult(cpvperp(r), body_.w));
100     cpFloat k       = k_scalar_body(body_, r, cpvnormalize_safe(v_centroid));
101     cpFloat damping = clippedArea * FLUID_DRAG * FLUID_DENSITY;
102     cpFloat v_coef  = cpfexp(-damping * dt * k); // linear drag
103     //	cpFloat v_coef = 1.0/(1.0 + damping*dt*cpvlength(v_centroid)*k); // quadratic drag
104     apply_impulse(body_, cpvmult(cpvsub(cpvmult(v_centroid, v_coef), v_centroid), 1.0 / k), r);
105 
106     // Apply angular damping for the fluid drag.
107     cpFloat w_damping = cpMomentForPoly(FLUID_DRAG * FLUID_DENSITY * clippedArea, clippedCount, clipped.ptr, cpvneg(body_.p));
108     body_.w *= cpfexp(-w_damping * dt * body_.i_inv);
109 
110     return cpTrue;
111 }
112 
113 static cpSpace* init()
114 {
115     ChipmunkDemoMessageString = messageBuffer[];
116 
117     cpSpace* space = cpSpaceNew();
118     cpSpaceSetIterations(space, 30);
119     cpSpaceSetGravity(space, cpv(0, -500));
120 
121     //	cpSpaceSetDamping(space, 0.5);
122     cpSpaceSetSleepTimeThreshold(space, 0.5f);
123     cpSpaceSetCollisionSlop(space, 0.5f);
124 
125     cpBody * body_;
126     cpBody * staticBody = cpSpaceGetStaticBody(space);
127     cpShape* shape;
128 
129     // Create segments around the edge of the screen.
130     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, -240), cpv(-320, 240), 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, -240), cpv(320, 240), 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, -240), cpv(320, -240), 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     {
151         // Add the edges of the bucket
152         cpBB bb        = cpBBNew(-300, -200, 100, 0);
153         cpFloat radius = 5.0f;
154 
155         shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(bb.l, bb.b), cpv(bb.l, bb.t), radius));
156         cpShapeSetElasticity(shape, 1.0f);
157         cpShapeSetFriction(shape, 1.0f);
158         cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
159 
160         shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(bb.r, bb.b), cpv(bb.r, bb.t), radius));
161         cpShapeSetElasticity(shape, 1.0f);
162         cpShapeSetFriction(shape, 1.0f);
163         cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
164 
165         shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(bb.l, bb.b), cpv(bb.r, bb.b), radius));
166         cpShapeSetElasticity(shape, 1.0f);
167         cpShapeSetFriction(shape, 1.0f);
168         cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
169 
170         // Add the sensor for the water.
171         shape = cpSpaceAddShape(space, cpBoxShapeNew2(staticBody, bb));
172         cpShapeSetSensor(shape, cpTrue);
173         cpShapeSetCollisionType(shape, 1);
174     }
175 
176     {
177         cpFloat width  = 200.0f;
178         cpFloat height = 50.0f;
179         cpFloat mass   = 0.3 * FLUID_DENSITY * width * height;
180         cpFloat moment = cpMomentForBox(mass, width, height);
181 
182         body_ = cpSpaceAddBody(space, cpBodyNew(mass, moment));
183         cpBodySetPos(body_, cpv(-50, -100));
184         cpBodySetVel(body_, cpv(0, -100));
185         cpBodySetAngVel(body_, 1);
186 
187         shape = cpSpaceAddShape(space, cpBoxShapeNew(body_, width, height));
188         cpShapeSetFriction(shape, 0.8f);
189     }
190 
191     {
192         cpFloat width  = 40.0f;
193         cpFloat height = width * 2;
194         cpFloat mass   = 0.3 * FLUID_DENSITY * width * height;
195         cpFloat moment = cpMomentForBox(mass, width, height);
196 
197         body_ = cpSpaceAddBody(space, cpBodyNew(mass, moment));
198         cpBodySetPos(body_, cpv(-200, -50));
199         cpBodySetVel(body_, cpv(0, -100));
200         cpBodySetAngVel(body_, 1);
201 
202         shape = cpSpaceAddShape(space, cpBoxShapeNew(body_, width, height));
203         cpShapeSetFriction(shape, 0.8f);
204     }
205 
206     cpSpaceAddCollisionHandler(space, 1, 0, null, safeCast!cpCollisionPreSolveFunc(&waterPreSolve), null, null, null);
207 
208     return space;
209 }
210 
211 static void destroy(cpSpace* space)
212 {
213     ChipmunkDemoFreeSpaceChildren(space);
214     cpSpaceFree(space);
215 }
216 
217 ChipmunkDemo Buoyancy = {
218     "Simple Sensor based fluids.",
219     1.0 / 180.0,
220     &init,
221     &update,
222     &ChipmunkDemoDefaultDrawImpl,
223     &destroy,
224 };