package com.badlogic.gdx.tests.bullet; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.VertexAttributes.Usage; import com.badlogic.gdx.graphics.g3d.Material; import com.badlogic.gdx.graphics.g3d.Model; import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; import com.badlogic.gdx.graphics.g3d.attributes.FloatAttribute; import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.physics.bullet.collision.btAxisSweep3; import com.badlogic.gdx.physics.bullet.collision.btBroadphaseProxy; import com.badlogic.gdx.physics.bullet.collision.btCapsuleShape; import com.badlogic.gdx.physics.bullet.collision.btCollisionDispatcher; import com.badlogic.gdx.physics.bullet.collision.btCollisionObject; import com.badlogic.gdx.physics.bullet.collision.btConvexShape; import com.badlogic.gdx.physics.bullet.collision.btDbvtBroadphase; import com.badlogic.gdx.physics.bullet.collision.btDefaultCollisionConfiguration; import com.badlogic.gdx.physics.bullet.collision.btGhostPairCallback; import com.badlogic.gdx.physics.bullet.collision.btPairCachingGhostObject; import com.badlogic.gdx.physics.bullet.dynamics.btDiscreteDynamicsWorld; import com.badlogic.gdx.physics.bullet.dynamics.btDynamicsWorld; import com.badlogic.gdx.physics.bullet.dynamics.btKinematicCharacterController; import com.badlogic.gdx.physics.bullet.dynamics.btSequentialImpulseConstraintSolver; public class CharacterTest extends BaseBulletTest { final int BOXCOUNT_X = 5; final int BOXCOUNT_Y = 5; final int BOXCOUNT_Z = 1; final float BOXOFFSET_X = -2.5f; final float BOXOFFSET_Y = 0.5f; final float BOXOFFSET_Z = 0f; BulletEntity ground; BulletEntity character; btGhostPairCallback ghostPairCallback; btPairCachingGhostObject ghostObject; btConvexShape ghostShape; btKinematicCharacterController characterController; Matrix4 characterTransform; Vector3 characterDirection = new Vector3(); Vector3 walkDirection = new Vector3(); @Override public BulletWorld createWorld () { // We create the world using an axis sweep broadphase for this test btDefaultCollisionConfiguration collisionConfiguration = new btDefaultCollisionConfiguration(); btCollisionDispatcher dispatcher = new btCollisionDispatcher(collisionConfiguration); btAxisSweep3 sweep = new btAxisSweep3(new Vector3(-1000, -1000, -1000), new Vector3(1000, 1000, 1000)); btSequentialImpulseConstraintSolver solver = new btSequentialImpulseConstraintSolver(); btDiscreteDynamicsWorld collisionWorld = new btDiscreteDynamicsWorld(dispatcher, sweep, solver, collisionConfiguration); ghostPairCallback = new btGhostPairCallback(); sweep.getOverlappingPairCache().setInternalGhostPairCallback(ghostPairCallback); return new BulletWorld(collisionConfiguration, dispatcher, sweep, solver, collisionWorld); } @Override public void create () { super.create(); instructions = "Tap to shoot\nArrow keys to move\nR to reset\nLong press to toggle debug mode\nSwipe for next test"; // Create a visual representation of the character (note that we don't use the physics part of BulletEntity, we'll do that manually) final Texture texture = new Texture(Gdx.files.internal("data/badlogic.jpg")); disposables.add(texture); final Material material = new Material(TextureAttribute.createDiffuse(texture), ColorAttribute.createSpecular(1,1,1,1), FloatAttribute.createShininess(8f)); final long attributes = Usage.Position | Usage.Normal | Usage.TextureCoordinates; final Model capsule = modelBuilder.createCapsule(2f, 6f, 16, material, attributes); disposables.add(capsule); world.addConstructor("capsule", new BulletConstructor(capsule, null)); character = world.add("capsule", 5f, 3f, 5f); characterTransform = character.transform; // Set by reference characterTransform.rotate(Vector3.X, 90); // Create the physics representation of the character ghostObject = new btPairCachingGhostObject(); ghostObject.setWorldTransform(characterTransform); ghostShape = new btCapsuleShape(2f, 2f); ghostObject.setCollisionShape(ghostShape); ghostObject.setCollisionFlags(btCollisionObject.CollisionFlags.CF_CHARACTER_OBJECT); characterController = new btKinematicCharacterController(ghostObject, ghostShape, .35f, Vector3.Y); // And add it to the physics world world.collisionWorld.addCollisionObject(ghostObject, (short)btBroadphaseProxy.CollisionFilterGroups.CharacterFilter, (short)(btBroadphaseProxy.CollisionFilterGroups.StaticFilter | btBroadphaseProxy.CollisionFilterGroups.DefaultFilter)); ((btDiscreteDynamicsWorld)(world.collisionWorld)).addAction(characterController); // Add the ground (ground = world.add("ground", 0f, 0f, 0f)) .setColor(0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 1f); // Create some boxes to play with for (int x = 0; x < BOXCOUNT_X; x++) { for (int y = 0; y < BOXCOUNT_Y; y++) { for (int z = 0; z < BOXCOUNT_Z; z++) { world.add("box", BOXOFFSET_X + x, BOXOFFSET_Y + y, BOXOFFSET_Z + z) .setColor(0.5f + 0.5f * (float)Math.random(), 0.5f + 0.5f * (float)Math.random(), 0.5f + 0.5f * (float)Math.random(), 1f); } } } } @Override public void update () { // If the left or right key is pressed, rotate the character and update its physics update accordingly. if (Gdx.input.isKeyPressed(Keys.LEFT)) { characterTransform.rotate(0, 1, 0, 5f); ghostObject.setWorldTransform(characterTransform); } if (Gdx.input.isKeyPressed(Keys.RIGHT)) { characterTransform.rotate(0, 1, 0, -5f); ghostObject.setWorldTransform(characterTransform); } // Fetch which direction the character is facing now characterDirection.set(-1,0,0).rot(characterTransform).nor(); // Set the walking direction accordingly (either forward or backward) walkDirection.set(0,0,0); if (Gdx.input.isKeyPressed(Keys.UP)) walkDirection.add(characterDirection); if (Gdx.input.isKeyPressed(Keys.DOWN)) walkDirection.add(-characterDirection.x, -characterDirection.y, -characterDirection.z); walkDirection.scl(4f * Gdx.graphics.getDeltaTime()); // And update the character controller characterController.setWalkDirection(walkDirection); // Now we can update the world as normally super.update(); // And fetch the new transformation of the character (this will make the model be rendered correctly) ghostObject.getWorldTransform(characterTransform); } @Override protected void renderWorld () { // TODO Auto-generated method stub super.renderWorld(); } @Override public boolean tap (float x, float y, int count, int button) { shoot(x, y); return true; } @Override public void dispose () { ((btDiscreteDynamicsWorld)(world.collisionWorld)).removeAction(characterController); world.collisionWorld.removeCollisionObject(ghostObject); super.dispose(); characterController.dispose(); ghostObject.dispose(); ghostShape.dispose(); ghostPairCallback.dispose(); ground = null; } }