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.Sticky; 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 enum 37 { 38 COLLIDE_STICK_SENSOR = 1, 39 }; 40 41 enum STICK_SENSOR_THICKNESS = 2.5f; 42 43 static void PostStepAddJoint(cpSpace* space, void* key, void* data) 44 { 45 // printf("Adding joint for %p\n", data); 46 47 cpConstraint* joint = cast(cpConstraint*)key; 48 cpSpaceAddConstraint(space, joint); 49 } 50 51 static cpBool StickyPreSolve(cpArbiter* arb, cpSpace* space, void* data) 52 { 53 // We want to fudge the collisions a bit to allow shapes to overlap more. 54 // This simulates their squishy sticky surface, and more importantly 55 // keeps them from separating and destroying the joint. 56 57 // Track the deepest collision point and use that to determine if a rigid collision should occur. 58 cpFloat deepest = INFINITY; 59 60 // Grab the contact set and iterate over them. 61 cpContactPointSet contacts = cpArbiterGetContactPointSet(arb); 62 63 for (int i = 0; i < contacts.count; i++) 64 { 65 // Increase the distance (negative means overlaping) of the 66 // collision to allow them to overlap more. 67 // This value is used only for fixing the positions of overlapping shapes. 68 cpFloat dist = contacts.points[i].dist + 2.0f * STICK_SENSOR_THICKNESS; 69 contacts.points[i].dist = cpfmin(0.0f, dist); 70 deepest = cpfmin(deepest, dist); 71 } 72 73 // Set the new contact point data. 74 cpArbiterSetContactPointSet(arb, &contacts); 75 76 // If the shapes are overlapping enough, then create a 77 // joint that sticks them together at the first contact point. 78 if (!cpArbiterGetUserData(arb) && deepest <= 0.0f) 79 { 80 mixin(CP_ARBITER_GET_BODIES!("arb", "bodyA", "bodyB")); 81 82 // Create a joint at the contact point to hold the body_ in place. 83 cpConstraint* joint = cpPivotJointNew(bodyA, bodyB, contacts.points[0].point); 84 85 // Give it a finite force for the stickyness. 86 cpConstraintSetMaxForce(joint, 3e3); 87 88 // Schedule a post-step() callback to add the joint. 89 cpSpaceAddPostStepCallback(space, &PostStepAddJoint, joint, null); 90 91 // Store the joint on the arbiter so we can remove it later. 92 cpArbiterSetUserData(arb, joint); 93 } 94 95 // Position correction and velocity are handled separately so changing 96 // the overlap distance alone won't prevent the collision from occuring. 97 // Explicitly the collision for this frame if the shapes don't overlap using the new distance. 98 return (deepest <= 0.0f); 99 100 // Lots more that you could improve upon here as well: 101 // * Modify the joint over time to make it plastic. 102 // * Modify the joint in the post-step to make it conditionally plastic (like clay). 103 // * Track a joint for the deepest contact point instead of the first. 104 // * Track a joint for each contact point. (more complicated since you only get one data pointer). 105 } 106 107 static void PostStepRemoveJoint(cpSpace* space, void* key, void* data) 108 { 109 // printf("Removing joint for %p\n", data); 110 111 cpConstraint* joint = cast(cpConstraint*)key; 112 cpSpaceRemoveConstraint(space, joint); 113 cpConstraintFree(joint); 114 } 115 116 static void StickySeparate(cpArbiter* arb, cpSpace* space, void* data) 117 { 118 cpConstraint* joint = cast(cpConstraint*)cpArbiterGetUserData(arb); 119 120 if (joint) 121 { 122 // The joint won't be removed until the step is done. 123 // Need to disable it so that it won't apply itself. 124 // Setting the force to 0 will do just that 125 cpConstraintSetMaxForce(joint, 0.0f); 126 127 // Perform the removal in a post-step() callback. 128 cpSpaceAddPostStepCallback(space, &PostStepRemoveJoint, joint, null); 129 130 // null out the reference to the joint. 131 // Not required, but it's a good practice. 132 cpArbiterSetUserData(arb, null); 133 } 134 } 135 136 static void update(cpSpace* space, double dt) 137 { 138 cpSpaceStep(space, dt); 139 } 140 141 static cpSpace* init() 142 { 143 ChipmunkDemoMessageString = "Sticky collisions using the cpArbiter data pointer.".dup; 144 145 cpSpace* space = cpSpaceNew(); 146 cpSpaceSetIterations(space, 10); 147 cpSpaceSetGravity(space, cpv(0, -1000)); 148 cpSpaceSetCollisionSlop(space, 2.0); 149 150 cpBody * staticBody = cpSpaceGetStaticBody(space); 151 cpShape* shape; 152 153 // Create segments around the edge of the screen. 154 shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-340, -260), cpv(-340, 260), 20.0f)); 155 cpShapeSetElasticity(shape, 1.0f); 156 cpShapeSetFriction(shape, 1.0f); 157 cpShapeSetCollisionType(shape, COLLIDE_STICK_SENSOR); 158 cpShapeSetLayers(shape, NOT_GRABABLE_MASK); 159 160 shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(340, -260), cpv(340, 260), 20.0f)); 161 cpShapeSetElasticity(shape, 1.0f); 162 cpShapeSetFriction(shape, 1.0f); 163 cpShapeSetCollisionType(shape, COLLIDE_STICK_SENSOR); 164 cpShapeSetLayers(shape, NOT_GRABABLE_MASK); 165 166 shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-340, -260), cpv(340, -260), 20.0f)); 167 cpShapeSetElasticity(shape, 1.0f); 168 cpShapeSetFriction(shape, 1.0f); 169 cpShapeSetCollisionType(shape, COLLIDE_STICK_SENSOR); 170 cpShapeSetLayers(shape, NOT_GRABABLE_MASK); 171 172 shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-340, 260), cpv(340, 260), 20.0f)); 173 cpShapeSetElasticity(shape, 1.0f); 174 cpShapeSetFriction(shape, 1.0f); 175 cpShapeSetCollisionType(shape, COLLIDE_STICK_SENSOR); 176 cpShapeSetLayers(shape, NOT_GRABABLE_MASK); 177 178 for (int i = 0; i < 200; i++) 179 { 180 cpFloat mass = 0.15f; 181 cpFloat radius = 10.0f; 182 183 cpBody* body_ = cpSpaceAddBody(space, cpBodyNew(mass, cpMomentForCircle(mass, 0.0f, radius, cpvzero))); 184 cpBodySetPos(body_, cpv(cpflerp(-150.0f, 150.0f, frand()), cpflerp(-150.0f, 150.0f, frand()))); 185 186 cpShape* shape1 = cpSpaceAddShape(space, cpCircleShapeNew(body_, radius + STICK_SENSOR_THICKNESS, cpvzero)); 187 cpShapeSetFriction(shape1, 0.9f); 188 cpShapeSetCollisionType(shape1, COLLIDE_STICK_SENSOR); 189 } 190 191 cpSpaceAddCollisionHandler(space, COLLIDE_STICK_SENSOR, COLLIDE_STICK_SENSOR, null, &StickyPreSolve, null, &StickySeparate, null); 192 193 return space; 194 } 195 196 static void destroy(cpSpace* space) 197 { 198 ChipmunkDemoFreeSpaceChildren(space); 199 cpSpaceFree(space); 200 } 201 202 ChipmunkDemo Sticky = { 203 "Sticky Surfaces", 204 1.0 / 60.0, 205 &init, 206 &update, 207 &ChipmunkDemoDefaultDrawImpl, 208 &destroy, 209 };