1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.tests.bullet; 18 19 import com.badlogic.gdx.Input.Buttons; 20 import com.badlogic.gdx.graphics.Color; 21 import com.badlogic.gdx.graphics.VertexAttributes.Usage; 22 import com.badlogic.gdx.graphics.g3d.Material; 23 import com.badlogic.gdx.graphics.g3d.Model; 24 import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; 25 import com.badlogic.gdx.math.MathUtils; 26 import com.badlogic.gdx.math.Matrix4; 27 import com.badlogic.gdx.math.Vector3; 28 import com.badlogic.gdx.math.collision.Ray; 29 import com.badlogic.gdx.physics.bullet.collision.Collision; 30 import com.badlogic.gdx.physics.bullet.collision.btCapsuleShape; 31 import com.badlogic.gdx.physics.bullet.collision.ClosestRayResultCallback; 32 import com.badlogic.gdx.physics.bullet.dynamics.btConeTwistConstraint; 33 import com.badlogic.gdx.physics.bullet.dynamics.btConstraintSetting; 34 import com.badlogic.gdx.physics.bullet.dynamics.btDynamicsWorld; 35 import com.badlogic.gdx.physics.bullet.dynamics.btHingeConstraint; 36 import com.badlogic.gdx.physics.bullet.dynamics.btPoint2PointConstraint; 37 import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody; 38 import com.badlogic.gdx.physics.bullet.dynamics.btTypedConstraint; 39 import com.badlogic.gdx.utils.Array; 40 41 /** @author xoppa */ 42 public class RayPickRagdollTest extends BaseBulletTest { 43 44 final Array<btTypedConstraint> constraints = new Array<btTypedConstraint>(); 45 btPoint2PointConstraint pickConstraint = null; 46 btRigidBody pickedBody = null; 47 float pickDistance; 48 Vector3 tmpV = new Vector3(); 49 50 @Override create()51 public void create () { 52 super.create(); 53 instructions = "Tap to shoot\nDrag ragdoll to pick\nLong press to toggle debug mode\nSwipe for next test\nCtrl+drag to rotate\nScroll to zoom"; 54 55 camera.position.set(4f, 2f, 4f); 56 camera.lookAt(0f, 1f, 0f); 57 camera.update(); 58 59 world.addConstructor("pelvis", new BulletConstructor(createCapsuleModel(0.15f, 0.2f), 1f, new btCapsuleShape(0.15f, 0.2f))); 60 world 61 .addConstructor("spine", new BulletConstructor(createCapsuleModel(0.15f, 0.28f), 1f, new btCapsuleShape(0.15f, 0.28f))); 62 world.addConstructor("head", new BulletConstructor(createCapsuleModel(0.1f, 0.05f), 1f, new btCapsuleShape(0.1f, 0.05f))); 63 world.addConstructor("upperleg", new BulletConstructor(createCapsuleModel(0.07f, 0.45f), 1f, new btCapsuleShape(0.07f, 64 0.45f))); 65 world.addConstructor("lowerleg", new BulletConstructor(createCapsuleModel(0.05f, 0.37f), 1f, new btCapsuleShape(0.05f, 66 0.37f))); 67 world.addConstructor("upperarm", new BulletConstructor(createCapsuleModel(0.05f, 0.33f), 1f, new btCapsuleShape(0.05f, 68 0.33f))); 69 world.addConstructor("lowerarm", new BulletConstructor(createCapsuleModel(0.04f, 0.25f), 1f, new btCapsuleShape(0.04f, 70 0.25f))); 71 72 world.add("ground", 0f, 0f, 0f).setColor(0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 73 0.25f + 0.5f * (float)Math.random(), 1f); 74 75 addRagdoll(0, 3f, 0); 76 addRagdoll(1f, 6f, 0); 77 addRagdoll(-1f, 12f, 0); 78 } 79 80 @Override dispose()81 public void dispose () { 82 for (int i = 0; i < constraints.size; i++) { 83 ((btDynamicsWorld)world.collisionWorld).removeConstraint(constraints.get(i)); 84 constraints.get(i).dispose(); 85 } 86 constraints.clear(); 87 super.dispose(); 88 } 89 90 @Override touchDown(int screenX, int screenY, int pointer, int button)91 public boolean touchDown (int screenX, int screenY, int pointer, int button) { 92 boolean result = false; 93 if (button == Buttons.LEFT) { 94 Ray ray = camera.getPickRay(screenX, screenY); 95 tmpV1.set(ray.direction).scl(10f).add(ray.origin); 96 ClosestRayResultCallback cb = new ClosestRayResultCallback(ray.origin, tmpV1); 97 world.collisionWorld.rayTest(ray.origin, tmpV1, cb); 98 if (cb.hasHit()) { 99 btRigidBody body = (btRigidBody)(cb.getCollisionObject()); 100 if (body != null && !body.isStaticObject() && !body.isKinematicObject()) { 101 pickedBody = body; 102 body.setActivationState(Collision.DISABLE_DEACTIVATION); 103 104 cb.getHitPointWorld(tmpV); 105 tmpV.mul(body.getCenterOfMassTransform().inv()); 106 107 pickConstraint = new btPoint2PointConstraint(body, tmpV); 108 btConstraintSetting setting = pickConstraint.getSetting(); 109 setting.setImpulseClamp(30f); 110 setting.setTau(0.001f); 111 pickConstraint.setSetting(setting); 112 113 ((btDynamicsWorld)world.collisionWorld).addConstraint(pickConstraint); 114 115 pickDistance = tmpV1.sub(camera.position).len(); 116 result = true; 117 } 118 } 119 cb.dispose(); 120 } 121 return result ? result : super.touchDown(screenX, screenY, pointer, button); 122 } 123 124 @Override touchUp(int screenX, int screenY, int pointer, int button)125 public boolean touchUp (int screenX, int screenY, int pointer, int button) { 126 boolean result = false; 127 if (button == Buttons.LEFT) { 128 if (pickConstraint != null) { 129 ((btDynamicsWorld)world.collisionWorld).removeConstraint(pickConstraint); 130 pickConstraint.dispose(); 131 pickConstraint = null; 132 result = true; 133 } 134 if (pickedBody != null) { 135 pickedBody.forceActivationState(Collision.ACTIVE_TAG); 136 pickedBody.setDeactivationTime(0f); 137 pickedBody = null; 138 } 139 } 140 return result ? result : super.touchUp(screenX, screenY, pointer, button); 141 } 142 143 @Override touchDragged(int screenX, int screenY, int pointer)144 public boolean touchDragged (int screenX, int screenY, int pointer) { 145 boolean result = false; 146 if (pickConstraint != null) { 147 Ray ray = camera.getPickRay(screenX, screenY); 148 tmpV1.set(ray.direction).scl(pickDistance).add(camera.position); 149 pickConstraint.setPivotB(tmpV1); 150 result = true; 151 } 152 return result ? result : super.touchDragged(screenX, screenY, pointer); 153 } 154 155 @Override tap(float x, float y, int count, int button)156 public boolean tap (float x, float y, int count, int button) { 157 shoot(x, y); 158 return true; 159 } 160 161 final static float PI = MathUtils.PI; 162 final static float PI2 = 0.5f * PI; 163 final static float PI4 = 0.25f * PI; 164 addRagdoll(final float x, final float y, final float z)165 public void addRagdoll (final float x, final float y, final float z) { 166 final Matrix4 tmpM = new Matrix4(); 167 btRigidBody pelvis = (btRigidBody)world.add("pelvis", x, y + 1, z).body; 168 btRigidBody spine = (btRigidBody)world.add("spine", x, y + 1.2f, z).body; 169 btRigidBody head = (btRigidBody)world.add("head", x, y + 1.6f, z).body; 170 btRigidBody leftupperleg = (btRigidBody)world.add("upperleg", x - 0.18f, y + 0.65f, z).body; 171 btRigidBody leftlowerleg = (btRigidBody)world.add("lowerleg", x - 0.18f, y + 0.2f, z).body; 172 btRigidBody rightupperleg = (btRigidBody)world.add("upperleg", x + 0.18f, y + 0.65f, z).body; 173 btRigidBody rightlowerleg = (btRigidBody)world.add("lowerleg", x + 0.18f, y + 0.2f, z).body; 174 btRigidBody leftupperarm = (btRigidBody)world.add("upperarm", 175 tmpM.setFromEulerAnglesRad(PI2, 0, 0).trn(x - 0.35f, y + 1.45f, z)).body; 176 btRigidBody leftlowerarm = (btRigidBody)world.add("lowerarm", tmpM.setFromEulerAnglesRad(PI2, 0, 0) 177 .trn(x - 0.7f, y + 1.45f, z)).body; 178 btRigidBody rightupperarm = (btRigidBody)world.add("upperarm", 179 tmpM.setFromEulerAnglesRad(-PI2, 0, 0).trn(x + 0.35f, y + 1.45f, z)).body; 180 btRigidBody rightlowerarm = (btRigidBody)world.add("lowerarm", 181 tmpM.setFromEulerAnglesRad(-PI2, 0, 0).trn(x + 0.7f, y + 1.45f, z)).body; 182 183 final Matrix4 localA = new Matrix4(); 184 final Matrix4 localB = new Matrix4(); 185 btHingeConstraint hingeC = null; 186 btConeTwistConstraint coneC = null; 187 188 // PelvisSpine 189 localA.setFromEulerAnglesRad(0, PI2, 0).trn(0, 0.15f, 0); 190 localB.setFromEulerAnglesRad(0, PI2, 0).trn(0, -0.15f, 0); 191 constraints.add(hingeC = new btHingeConstraint(pelvis, spine, localA, localB)); 192 hingeC.setLimit(-PI4, PI2); 193 ((btDynamicsWorld)world.collisionWorld).addConstraint(hingeC, true); 194 195 // SpineHead 196 localA.setFromEulerAnglesRad(PI2, 0, 0).trn(0, 0.3f, 0); 197 localB.setFromEulerAnglesRad(PI2, 0, 0).trn(0, -0.14f, 0); 198 constraints.add(coneC = new btConeTwistConstraint(spine, head, localA, localB)); 199 coneC.setLimit(PI4, PI4, PI2); 200 ((btDynamicsWorld)world.collisionWorld).addConstraint(coneC, true); 201 202 // LeftHip 203 localA.setFromEulerAnglesRad(-PI4 * 5f, 0, 0).trn(-0.18f, -0.1f, 0); 204 localB.setFromEulerAnglesRad(-PI4 * 5f, 0, 0).trn(0, 0.225f, 0); 205 constraints.add(coneC = new btConeTwistConstraint(pelvis, leftupperleg, localA, localB)); 206 coneC.setLimit(PI4, PI4, 0); 207 ((btDynamicsWorld)world.collisionWorld).addConstraint(coneC, true); 208 209 // LeftKnee 210 localA.setFromEulerAnglesRad(0, PI2, 0).trn(0, -0.225f, 0); 211 localB.setFromEulerAnglesRad(0, PI2, 0).trn(0, 0.185f, 0); 212 constraints.add(hingeC = new btHingeConstraint(leftupperleg, leftlowerleg, localA, localB)); 213 hingeC.setLimit(0, PI2); 214 ((btDynamicsWorld)world.collisionWorld).addConstraint(hingeC, true); 215 216 // RightHip 217 localA.setFromEulerAnglesRad(-PI4 * 5f, 0, 0).trn(0.18f, -0.1f, 0); 218 localB.setFromEulerAnglesRad(-PI4 * 5f, 0, 0).trn(0, 0.225f, 0); 219 constraints.add(coneC = new btConeTwistConstraint(pelvis, rightupperleg, localA, localB)); 220 coneC.setLimit(PI4, PI4, 0); 221 ((btDynamicsWorld)world.collisionWorld).addConstraint(coneC, true); 222 223 // RightKnee 224 localA.setFromEulerAnglesRad(0, PI2, 0).trn(0, -0.225f, 0); 225 localB.setFromEulerAnglesRad(0, PI2, 0).trn(0, 0.185f, 0); 226 constraints.add(hingeC = new btHingeConstraint(rightupperleg, rightlowerleg, localA, localB)); 227 hingeC.setLimit(0, PI2); 228 ((btDynamicsWorld)world.collisionWorld).addConstraint(hingeC, true); 229 230 // LeftShoulder 231 localA.setFromEulerAnglesRad(PI, 0, 0).trn(-0.2f, 0.15f, 0); 232 localB.setFromEulerAnglesRad(PI2, 0, 0).trn(0, -0.18f, 0); 233 constraints.add(coneC = new btConeTwistConstraint(pelvis, leftupperarm, localA, localB)); 234 coneC.setLimit(PI2, PI2, 0); 235 ((btDynamicsWorld)world.collisionWorld).addConstraint(coneC, true); 236 237 // LeftElbow 238 localA.setFromEulerAnglesRad(0, PI2, 0).trn(0, 0.18f, 0); 239 localB.setFromEulerAnglesRad(0, PI2, 0).trn(0, -0.14f, 0); 240 constraints.add(hingeC = new btHingeConstraint(leftupperarm, leftlowerarm, localA, localB)); 241 hingeC.setLimit(0, PI2); 242 ((btDynamicsWorld)world.collisionWorld).addConstraint(hingeC, true); 243 244 // RightShoulder 245 localA.setFromEulerAnglesRad(PI, 0, 0).trn(0.2f, 0.15f, 0); 246 localB.setFromEulerAnglesRad(PI2, 0, 0).trn(0, -0.18f, 0); 247 constraints.add(coneC = new btConeTwistConstraint(pelvis, rightupperarm, localA, localB)); 248 coneC.setLimit(PI2, PI2, 0); 249 ((btDynamicsWorld)world.collisionWorld).addConstraint(coneC, true); 250 251 // RightElbow 252 localA.setFromEulerAnglesRad(0, PI2, 0).trn(0, 0.18f, 0); 253 localB.setFromEulerAnglesRad(0, PI2, 0).trn(0, -0.14f, 0); 254 constraints.add(hingeC = new btHingeConstraint(rightupperarm, rightlowerarm, localA, localB)); 255 hingeC.setLimit(0, PI2); 256 ((btDynamicsWorld)world.collisionWorld).addConstraint(hingeC, true); 257 } 258 createCapsuleModel(float radius, float height)259 protected Model createCapsuleModel (float radius, float height) { 260 final Model result = modelBuilder.createCapsule(radius, height + radius * 2f, 16, 261 new Material(ColorAttribute.createDiffuse(Color.WHITE), ColorAttribute.createSpecular(Color.WHITE)), Usage.Position 262 | Usage.Normal); 263 disposables.add(result); 264 return result; 265 } 266 } 267