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.Slice;
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 DENSITY = (1.0 / 10000.0);
37 
38 static void ClipPoly(cpSpace* space, cpShape* shape, cpVect n, cpFloat dist)
39 {
40     cpBody* body_ = cpShapeGetBody(shape);
41 
42     int count        = cpPolyShapeGetNumVerts(shape);
43     int clippedCount = 0;
44 
45     cpVect* clipped = cast(cpVect*)alloca((count + 1) * cpVect.sizeof);
46 
47     for (int i = 0, j = count - 1; i < count; j = i, i++)
48     {
49         cpVect  a      = cpBodyLocal2World(body_, cpPolyShapeGetVert(shape, j));
50         cpFloat a_dist = cpvdot(a, n) - dist;
51 
52         if (a_dist < 0.0)
53         {
54             clipped[clippedCount] = a;
55             clippedCount++;
56         }
57 
58         cpVect  b      = cpBodyLocal2World(body_, cpPolyShapeGetVert(shape, i));
59         cpFloat b_dist = cpvdot(b, n) - dist;
60 
61         if (a_dist * b_dist < 0.0f)
62         {
63             cpFloat t = cpfabs(a_dist) / (cpfabs(a_dist) + cpfabs(b_dist));
64 
65             clipped[clippedCount] = cpvlerp(a, b, t);
66             clippedCount++;
67         }
68     }
69 
70     cpVect  centroid = cpCentroidForPoly(clippedCount, clipped);
71     cpFloat mass     = cpAreaForPoly(clippedCount, clipped) * DENSITY;
72     cpFloat moment   = cpMomentForPoly(mass, clippedCount, clipped, cpvneg(centroid));
73 
74     cpBody* new_body = cpSpaceAddBody(space, cpBodyNew(mass, moment));
75     cpBodySetPos(new_body, centroid);
76     cpBodySetVel(new_body, cpBodyGetVelAtWorldPoint(body_, centroid));
77     cpBodySetAngVel(new_body, cpBodyGetAngVel(body_));
78 
79     cpShape* new_shape = cpSpaceAddShape(space, cpPolyShapeNew(new_body, clippedCount, clipped, cpvneg(centroid)));
80 
81     // Copy whatever properties you have set on the original shape that are important
82     cpShapeSetFriction(new_shape, cpShapeGetFriction(shape));
83 }
84 
85 // Context structs are annoying, use blocks or closures instead if your compiler supports them.
86 struct SliceContext
87 {
88     cpVect a, b;
89     cpSpace* space;
90 };
91 
92 static void SliceShapePostStep(cpSpace* space, cpShape* shape, SliceContext* context)
93 {
94     cpVect a = context.a;
95     cpVect b = context.b;
96 
97     // Clipping plane normal and distance.
98     cpVect  n    = cpvnormalize(cpvperp(cpvsub(b, a)));
99     cpFloat dist = cpvdot(a, n);
100 
101     ClipPoly(space, shape, n, dist);
102     ClipPoly(space, shape, cpvneg(n), -dist);
103 
104     cpBody* body_ = cpShapeGetBody(shape);
105     cpSpaceRemoveShape(space, shape);
106     cpSpaceRemoveBody(space, body_);
107     cpShapeFree(shape);
108     cpBodyFree(body_);
109 }
110 
111 static void SliceQuery(cpShape* shape, cpFloat t, cpVect n, SliceContext* context)
112 {
113     cpVect a = context.a;
114     cpVect b = context.b;
115 
116     // Check that the slice was complete by checking that the endpoints aren't in the sliced shape.
117     if (!cpShapePointQuery(shape, a) && !cpShapePointQuery(shape, b))
118     {
119         // Can't modify the space during a query.
120         // Must make a post-step callback to do the actual slicing.
121         cpSpaceAddPostStepCallback(context.space, safeCast!cpPostStepFunc(&SliceShapePostStep), shape, context);
122     }
123 }
124 
125 static void update(cpSpace* space, double dt)
126 {
127     cpSpaceStep(space, dt);
128 
129     static cpBool lastClickState = cpFalse;
130     static cpVect sliceStart     = { 0.0, 0.0 };
131 
132     // Annoying state tracking code that you wouldn't need
133     // in a real event driven system.
134     if (ChipmunkDemoRightClick != lastClickState)
135     {
136         if (ChipmunkDemoRightClick)
137         {
138             // MouseDown
139             sliceStart = ChipmunkDemoMouse;
140         }
141         else
142         {
143             // MouseUp
144             SliceContext context = { sliceStart, ChipmunkDemoMouse, space };
145             cpSpaceSegmentQuery(space, sliceStart, ChipmunkDemoMouse, GRABABLE_MASK_BIT, CP_NO_GROUP, safeCast!cpSpaceSegmentQueryFunc(&SliceQuery), &context);
146         }
147 
148         lastClickState = ChipmunkDemoRightClick;
149     }
150 
151     if (ChipmunkDemoRightClick)
152     {
153         ChipmunkDebugDrawSegment(sliceStart, ChipmunkDemoMouse, RGBAColor(1, 0, 0, 1));
154     }
155 }
156 
157 static cpSpace* init()
158 {
159     ChipmunkDemoMessageString = "Right click and drag to slice up the block.".dup;
160 
161     cpSpace* space = cpSpaceNew();
162     cpSpaceSetIterations(space, 30);
163     cpSpaceSetGravity(space, cpv(0, -500));
164     cpSpaceSetSleepTimeThreshold(space, 0.5f);
165     cpSpaceSetCollisionSlop(space, 0.5f);
166 
167     cpBody * body_;
168     cpBody * staticBody = cpSpaceGetStaticBody(space);
169     cpShape* shape;
170 
171     // Create segments around the edge of the screen.
172     shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-1000, -240), cpv(1000, -240), 0.0f));
173     cpShapeSetElasticity(shape, 1.0f);
174     cpShapeSetFriction(shape, 1.0f);
175     cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
176 
177     cpFloat width  = 200.0f;
178     cpFloat height = 300.0f;
179     cpFloat mass   = width * height * DENSITY;
180     cpFloat moment = cpMomentForBox(mass, width, height);
181 
182     body_ = cpSpaceAddBody(space, cpBodyNew(mass, moment));
183 
184     shape = cpSpaceAddShape(space, cpBoxShapeNew(body_, width, height));
185     cpShapeSetFriction(shape, 0.6f);
186 
187     return space;
188 }
189 
190 static void destroy(cpSpace* space)
191 {
192     ChipmunkDemoFreeSpaceChildren(space);
193     cpSpaceFree(space);
194 }
195 
196 ChipmunkDemo Slice = {
197     "Slice.",
198     1.0 / 60.0,
199     &init,
200     &update,
201     &ChipmunkDemoDefaultDrawImpl,
202     &destroy,
203 };