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