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.Unicycle; 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 cpBody* balance_body; 37 static cpFloat balance_sin = 0.0; 38 39 //static cpFloat last_v = 0.0; 40 41 static cpBody* wheel_body; 42 static cpConstraint* motor; 43 44 /* 45 TODO 46 - Clamp max angle dynamically based on output torque. 47 */ 48 49 static void motor_preSolve(cpConstraint* motor, cpSpace* space) 50 { 51 cpFloat dt = cpSpaceGetCurrentTimeStep(space); 52 53 cpFloat target_x = ChipmunkDemoMouse.x; 54 ChipmunkDebugDrawSegment(cpv(target_x, -1000.0), cpv(target_x, 1000.0), RGBAColor(1.0, 0.0, 0.0, 1.0)); 55 56 cpFloat max_v = 500.0; 57 cpFloat target_v = cpfclamp(bias_coef(0.5, dt / 1.2) * (target_x - balance_body.p.x) / dt, -max_v, max_v); 58 cpFloat error_v = (target_v - balance_body.v.x); 59 cpFloat target_sin = 3.0e-3 * bias_coef(0.1, dt) * error_v / dt; 60 61 cpFloat max_sin = cpfsin(0.6); 62 balance_sin = cpfclamp(balance_sin - 6.0e-5 * bias_coef(0.2, dt) * error_v / dt, -max_sin, max_sin); 63 cpFloat target_a = asin(cpfclamp(-target_sin + balance_sin, -max_sin, max_sin)); 64 cpFloat angular_diff = asin(cpvcross(balance_body.rot, cpvforangle(target_a))); 65 cpFloat target_w = bias_coef(0.1, dt / 0.4) * (angular_diff) / dt; 66 67 cpFloat max_rate = 50.0; 68 cpFloat rate = cpfclamp(wheel_body.w + balance_body.w - target_w, -max_rate, max_rate); 69 cpSimpleMotorSetRate(motor, cpfclamp(rate, -max_rate, max_rate)); 70 cpConstraintSetMaxForce(motor, 8.0e4); 71 } 72 73 static void update(cpSpace* space, double dt) 74 { 75 cpSpaceStep(space, dt); 76 } 77 78 static cpSpace* init() 79 { 80 ChipmunkDemoMessageString = "This unicycle is completely driven and balanced by a single cpSimpleMotor.\nMove the mouse to make the unicycle follow it.".dup; 81 82 cpSpace* space = cpSpaceNew(); 83 cpSpaceSetIterations(space, 30); 84 cpSpaceSetGravity(space, cpv(0, -500)); 85 86 { 87 cpShape* shape = null; 88 cpBody * staticBody = space.staticBody; 89 90 shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-3200, -240), cpv(3200, -240), 0.0f)); 91 cpShapeSetElasticity(shape, 1.0f); 92 cpShapeSetFriction(shape, 1.0f); 93 cpShapeSetLayers(shape, NOT_GRABABLE_MASK); 94 95 shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(0, -200), cpv(240, -240), 0.0f)); 96 cpShapeSetElasticity(shape, 1.0f); 97 cpShapeSetFriction(shape, 1.0f); 98 cpShapeSetLayers(shape, NOT_GRABABLE_MASK); 99 100 shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-240, -240), cpv(0, -200), 0.0f)); 101 cpShapeSetElasticity(shape, 1.0f); 102 cpShapeSetFriction(shape, 1.0f); 103 cpShapeSetLayers(shape, NOT_GRABABLE_MASK); 104 } 105 106 { 107 cpFloat radius = 20.0; 108 cpFloat mass = 1.0; 109 110 cpFloat moment = cpMomentForCircle(mass, 0.0, radius, cpvzero); 111 wheel_body = cpSpaceAddBody(space, cpBodyNew(mass, moment)); 112 wheel_body.p = cpv(0.0, -160.0 + radius); 113 114 cpShape* shape = cpSpaceAddShape(space, cpCircleShapeNew(wheel_body, radius, cpvzero)); 115 shape.u = 0.7; 116 shape.group = 1; 117 } 118 119 { 120 cpFloat cog_offset = 30.0; 121 122 cpBB bb1 = cpBBNew(-5.0, 0.0 - cog_offset, 5.0, cog_offset * 1.2 - cog_offset); 123 cpBB bb2 = cpBBNew(-25.0, bb1.t, 25.0, bb1.t + 10.0); 124 125 cpFloat mass = 3.0; 126 cpFloat moment = cpMomentForBox2(mass, bb1) + cpMomentForBox2(mass, bb2); 127 128 balance_body = cpSpaceAddBody(space, cpBodyNew(mass, moment)); 129 balance_body.p = cpv(0.0, wheel_body.p.y + cog_offset); 130 131 cpShape* shape = null; 132 133 shape = cpSpaceAddShape(space, cpBoxShapeNew2(balance_body, bb1)); 134 shape.u = 1.0; 135 shape.group = 1; 136 137 shape = cpSpaceAddShape(space, cpBoxShapeNew2(balance_body, bb2)); 138 shape.u = 1.0; 139 shape.group = 1; 140 } 141 142 cpVect anchr1 = cpBodyWorld2Local(balance_body, wheel_body.p); 143 cpVect groove_a = cpvadd(anchr1, cpv(0.0, 30.0)); 144 cpVect groove_b = cpvadd(anchr1, cpv(0.0, -10.0)); 145 cpSpaceAddConstraint(space, cpGrooveJointNew(balance_body, wheel_body, groove_a, groove_b, cpvzero)); 146 cpSpaceAddConstraint(space, cpDampedSpringNew(balance_body, wheel_body, anchr1, cpvzero, 0.0, 6.0e2, 30.0)); 147 148 motor = cpSpaceAddConstraint(space, cpSimpleMotorNew(wheel_body, balance_body, 0.0)); 149 motor.preSolve = &motor_preSolve; 150 151 { 152 cpFloat width = 100.0; 153 cpFloat height = 20.0; 154 cpFloat mass = 3.0; 155 156 cpBody* boxBody = cpSpaceAddBody(space, cpBodyNew(mass, cpMomentForBox(mass, width, height))); 157 cpBodySetPos(boxBody, cpv(200, -100)); 158 159 cpShape* shape = cpSpaceAddShape(space, cpBoxShapeNew(boxBody, width, height)); 160 cpShapeSetFriction(shape, 0.7); 161 } 162 163 return space; 164 } 165 166 static void destroy(cpSpace* space) 167 { 168 ChipmunkDemoFreeSpaceChildren(space); 169 cpSpaceFree(space); 170 } 171 172 ChipmunkDemo Unicycle = { 173 "Unicycle", 174 1.0 / 60.0, 175 &init, 176 &update, 177 &ChipmunkDemoDefaultDrawImpl, 178 &destroy, 179 };