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 };