/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.tests; import java.util.ArrayList; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.physics.box2d.Body; import com.badlogic.gdx.physics.box2d.BodyDef; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer; import com.badlogic.gdx.physics.box2d.ChainShape; import com.badlogic.gdx.physics.box2d.Contact; import com.badlogic.gdx.physics.box2d.ContactImpulse; import com.badlogic.gdx.physics.box2d.ContactListener; import com.badlogic.gdx.physics.box2d.Fixture; import com.badlogic.gdx.physics.box2d.FixtureDef; import com.badlogic.gdx.physics.box2d.Manifold; import com.badlogic.gdx.physics.box2d.PolygonShape; import com.badlogic.gdx.physics.box2d.QueryCallback; import com.badlogic.gdx.physics.box2d.World; import com.badlogic.gdx.physics.box2d.WorldManifold; import com.badlogic.gdx.physics.box2d.joints.MouseJoint; import com.badlogic.gdx.physics.box2d.joints.MouseJointDef; import com.badlogic.gdx.tests.utils.GdxTest; import com.badlogic.gdx.utils.TimeUtils; public class Box2DTest extends GdxTest implements InputProcessor { /** the camera **/ private com.badlogic.gdx.graphics.OrthographicCamera camera; /** the immediate mode renderer to output our debug drawings **/ private ShapeRenderer renderer; /** box2d debug renderer **/ private Box2DDebugRenderer debugRenderer; /** a spritebatch and a font for text rendering and a Texture to draw our boxes **/ private SpriteBatch batch; private BitmapFont font; private TextureRegion textureRegion; /** our box2D world **/ private World world; /** our boxes **/ private ArrayList<Body> boxes = new ArrayList<Body>(); /** our ground box **/ Body groundBody; /** our mouse joint **/ private MouseJoint mouseJoint = null; /** a hit body **/ Body hitBody = null; @Override public void create () { // setup the camera. In Box2D we operate on a // meter scale, pixels won't do it. So we use // an orthographic camera with a viewport of // 48 meters in width and 32 meters in height. // We also position the camera so that it // looks at (0,16) (that's where the middle of the // screen will be located). camera = new OrthographicCamera(48, 32); camera.position.set(0, 16, 0); // next we setup the immediate mode renderer renderer = new ShapeRenderer(); // next we create the box2d debug renderer debugRenderer = new Box2DDebugRenderer(); // next we create a SpriteBatch and a font batch = new SpriteBatch(); font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"), false); font.setColor(Color.RED); textureRegion = new TextureRegion(new Texture(Gdx.files.internal("data/badlogicsmall.jpg"))); // next we create out physics world. createPhysicsWorld(); // register ourselfs as an InputProcessor Gdx.input.setInputProcessor(this); } private void createPhysicsWorld () { // we instantiate a new World with a proper gravity vector // and tell it to sleep when possible. world = new World(new Vector2(0, -10), true); float[] vertices = {-0.07421887f, -0.16276085f, -0.12109375f, -0.22786504f, -0.157552f, -0.7122401f, 0.04296875f, -0.7122401f, 0.110677004f, -0.6419276f, 0.13151026f, -0.49869835f, 0.08984375f, -0.3190109f}; PolygonShape shape = new PolygonShape(); shape.set(vertices); // next we create a static ground platform. This platform // is not moveable and will not react to any influences from // outside. It will however influence other bodies. First we // create a PolygonShape that holds the form of the platform. // it will be 100 meters wide and 2 meters high, centered // around the origin PolygonShape groundPoly = new PolygonShape(); groundPoly.setAsBox(50, 1); // next we create the body for the ground platform. It's // simply a static body. BodyDef groundBodyDef = new BodyDef(); groundBodyDef.type = BodyType.StaticBody; groundBody = world.createBody(groundBodyDef); // finally we add a fixture to the body using the polygon // defined above. Note that we have to dispose PolygonShapes // and CircleShapes once they are no longer used. This is the // only time you have to care explicitly for memory management. FixtureDef fixtureDef = new FixtureDef(); fixtureDef.shape = groundPoly; fixtureDef.filter.groupIndex = 0; groundBody.createFixture(fixtureDef); groundPoly.dispose(); // We also create a simple ChainShape we put above our // ground polygon for extra funkyness. ChainShape chainShape = new ChainShape(); chainShape.createLoop(new Vector2[] {new Vector2(-10, 10), new Vector2(-10, 5), new Vector2(10, 5), new Vector2(10, 11),}); BodyDef chainBodyDef = new BodyDef(); chainBodyDef.type = BodyType.StaticBody; Body chainBody = world.createBody(chainBodyDef); chainBody.createFixture(chainShape, 0); chainShape.dispose(); createBoxes(); // You can savely ignore the rest of this method :) world.setContactListener(new ContactListener() { @Override public void beginContact (Contact contact) { // System.out.println("begin contact"); } @Override public void endContact (Contact contact) { // System.out.println("end contact"); } @Override public void preSolve (Contact contact, Manifold oldManifold) { // Manifold.ManifoldType type = oldManifold.getType(); // Vector2 localPoint = oldManifold.getLocalPoint(); // Vector2 localNormal = oldManifold.getLocalNormal(); // int pointCount = oldManifold.getPointCount(); // ManifoldPoint[] points = oldManifold.getPoints(); // System.out.println("pre solve, " + type + // ", point: " + localPoint + // ", local normal: " + localNormal + // ", #points: " + pointCount + // ", [" + points[0] + ", " + points[1] + "]"); } @Override public void postSolve (Contact contact, ContactImpulse impulse) { // float[] ni = impulse.getNormalImpulses(); // float[] ti = impulse.getTangentImpulses(); // System.out.println("post solve, normal impulses: " + ni[0] + ", " + ni[1] + ", tangent impulses: " + ti[0] + ", " + ti[1]); } }); } private void createBoxes () { // next we create 50 boxes at random locations above the ground // body. First we create a nice polygon representing a box 2 meters // wide and high. PolygonShape boxPoly = new PolygonShape(); boxPoly.setAsBox(1, 1); // next we create the 50 box bodies using the PolygonShape we just // defined. This process is similar to the one we used for the ground // body. Note that we reuse the polygon for each body fixture. for (int i = 0; i < 20; i++) { // Create the BodyDef, set a random position above the // ground and create a new body BodyDef boxBodyDef = new BodyDef(); boxBodyDef.type = BodyType.DynamicBody; boxBodyDef.position.x = -24 + (float)(Math.random() * 48); boxBodyDef.position.y = 10 + (float)(Math.random() * 100); Body boxBody = world.createBody(boxBodyDef); boxBody.createFixture(boxPoly, 1); // add the box to our list of boxes boxes.add(boxBody); } // we are done, all that's left is disposing the boxPoly boxPoly.dispose(); } @Override public void render () { // first we update the world. For simplicity // we use the delta time provided by the Graphics // instance. Normally you'll want to fix the time // step. long start = TimeUtils.nanoTime(); world.step(Gdx.graphics.getDeltaTime(), 8, 3); float updateTime = (TimeUtils.nanoTime() - start) / 1000000000.0f; // next we clear the color buffer and set the camera // matrices Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); camera.update(); // next we render the ground body renderBox(groundBody, 50, 1); // next we render each box via the SpriteBatch. // for this we have to set the projection matrix of the // spritebatch to the camera's combined matrix. This will // make the spritebatch work in world coordinates batch.getProjectionMatrix().set(camera.combined); batch.begin(); for (int i = 0; i < boxes.size(); i++) { Body box = boxes.get(i); Vector2 position = box.getPosition(); // that's the box's center position float angle = MathUtils.radiansToDegrees * box.getAngle(); // the rotation angle around the center batch.draw(textureRegion, position.x - 1, position.y - 1, // the bottom left corner of the box, unrotated 1f, 1f, // the rotation center relative to the bottom left corner of the box 2, 2, // the width and height of the box 1, 1, // the scale on the x- and y-axis angle); // the rotation angle } batch.end(); // next we use the debug renderer. Note that we // simply apply the camera again and then call // the renderer. the camera.apply() call is actually // not needed as the opengl matrices are already set // by the spritebatch which in turn uses the camera matrices :) debugRenderer.render(world, camera.combined); // finally we render all contact points renderer.setProjectionMatrix(camera.combined); renderer.begin(ShapeType.Point); renderer.setColor(0, 1, 0, 1); for (int i = 0; i < world.getContactCount(); i++) { Contact contact = world.getContactList().get(i); // we only render the contact if it actually touches if (contact.isTouching()) { // get the world manifold from which we get the // contact points. A manifold can have 0, 1 or 2 // contact points. WorldManifold manifold = contact.getWorldManifold(); int numContactPoints = manifold.getNumberOfContactPoints(); for (int j = 0; j < numContactPoints; j++) { Vector2 point = manifold.getPoints()[j]; renderer.point(point.x, point.y, 0); } } } renderer.end(); // finally we render the time it took to update the world // for this we have to set the projection matrix again, so // we work in pixel coordinates batch.getProjectionMatrix().setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); batch.begin(); font.draw(batch, "fps: " + Gdx.graphics.getFramesPerSecond() + " update time: " + updateTime, 0, 20); batch.end(); } Matrix4 transform = new Matrix4(); private void renderBox (Body body, float halfWidth, float halfHeight) { // get the bodies center and angle in world coordinates Vector2 pos = body.getWorldCenter(); float angle = body.getAngle(); // set the translation and rotation matrix transform.setToTranslation(pos.x, pos.y, 0); transform.rotate(0, 0, 1, (float)Math.toDegrees(angle)); // render the box renderer.begin(ShapeType.Line); renderer.setTransformMatrix(transform); renderer.setColor(1, 1, 1, 1); renderer.rect(-halfWidth, -halfHeight, halfWidth * 2, halfHeight * 2); renderer.end(); } /** we instantiate this vector and the callback here so we don't irritate the GC **/ Vector3 testPoint = new Vector3(); QueryCallback callback = new QueryCallback() { @Override public boolean reportFixture (Fixture fixture) { // if the hit fixture's body is the ground body // we ignore it if (fixture.getBody() == groundBody) return true; // if the hit point is inside the fixture of the body // we report it if (fixture.testPoint(testPoint.x, testPoint.y)) { hitBody = fixture.getBody(); return false; } else return true; } }; @Override public boolean touchDown (int x, int y, int pointer, int newParam) { // translate the mouse coordinates to world coordinates testPoint.set(x, y, 0); camera.unproject(testPoint); // ask the world which bodies are within the given // bounding box around the mouse pointer hitBody = null; world.QueryAABB(callback, testPoint.x - 0.1f, testPoint.y - 0.1f, testPoint.x + 0.1f, testPoint.y + 0.1f); // if we hit something we create a new mouse joint // and attach it to the hit body. if (hitBody != null) { MouseJointDef def = new MouseJointDef(); def.bodyA = groundBody; def.bodyB = hitBody; def.collideConnected = true; def.target.set(testPoint.x, testPoint.y); def.maxForce = 1000.0f * hitBody.getMass(); mouseJoint = (MouseJoint)world.createJoint(def); hitBody.setAwake(true); } else { for (Body box : boxes) world.destroyBody(box); boxes.clear(); createBoxes(); } return false; } /** another temporary vector **/ Vector2 target = new Vector2(); @Override public boolean touchDragged (int x, int y, int pointer) { // if a mouse joint exists we simply update // the target of the joint based on the new // mouse coordinates if (mouseJoint != null) { camera.unproject(testPoint.set(x, y, 0)); mouseJoint.setTarget(target.set(testPoint.x, testPoint.y)); } return false; } @Override public boolean touchUp (int x, int y, int pointer, int button) { // if a mouse joint exists we simply destroy it if (mouseJoint != null) { world.destroyJoint(mouseJoint); mouseJoint = null; } return false; } @Override public void dispose () { world.dispose(); renderer.dispose(); debugRenderer.dispose(); font.dispose(); textureRegion.getTexture().dispose(); } }