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; 18 19 import java.util.ArrayList; 20 21 import com.badlogic.gdx.Gdx; 22 import com.badlogic.gdx.InputProcessor; 23 import com.badlogic.gdx.graphics.Color; 24 import com.badlogic.gdx.graphics.GL20; 25 import com.badlogic.gdx.graphics.OrthographicCamera; 26 import com.badlogic.gdx.graphics.Texture; 27 import com.badlogic.gdx.graphics.g2d.BitmapFont; 28 import com.badlogic.gdx.graphics.g2d.SpriteBatch; 29 import com.badlogic.gdx.graphics.g2d.TextureRegion; 30 import com.badlogic.gdx.graphics.glutils.ShapeRenderer; 31 import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; 32 import com.badlogic.gdx.math.MathUtils; 33 import com.badlogic.gdx.math.Matrix4; 34 import com.badlogic.gdx.math.Vector2; 35 import com.badlogic.gdx.math.Vector3; 36 import com.badlogic.gdx.physics.box2d.Body; 37 import com.badlogic.gdx.physics.box2d.BodyDef; 38 import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; 39 import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer; 40 import com.badlogic.gdx.physics.box2d.ChainShape; 41 import com.badlogic.gdx.physics.box2d.Contact; 42 import com.badlogic.gdx.physics.box2d.ContactImpulse; 43 import com.badlogic.gdx.physics.box2d.ContactListener; 44 import com.badlogic.gdx.physics.box2d.Fixture; 45 import com.badlogic.gdx.physics.box2d.FixtureDef; 46 import com.badlogic.gdx.physics.box2d.Manifold; 47 import com.badlogic.gdx.physics.box2d.PolygonShape; 48 import com.badlogic.gdx.physics.box2d.QueryCallback; 49 import com.badlogic.gdx.physics.box2d.World; 50 import com.badlogic.gdx.physics.box2d.WorldManifold; 51 import com.badlogic.gdx.physics.box2d.joints.MouseJoint; 52 import com.badlogic.gdx.physics.box2d.joints.MouseJointDef; 53 import com.badlogic.gdx.tests.utils.GdxTest; 54 import com.badlogic.gdx.utils.TimeUtils; 55 56 public class Box2DTest extends GdxTest implements InputProcessor { 57 /** the camera **/ 58 private com.badlogic.gdx.graphics.OrthographicCamera camera; 59 60 /** the immediate mode renderer to output our debug drawings **/ 61 private ShapeRenderer renderer; 62 63 /** box2d debug renderer **/ 64 private Box2DDebugRenderer debugRenderer; 65 66 /** a spritebatch and a font for text rendering and a Texture to draw our boxes **/ 67 private SpriteBatch batch; 68 private BitmapFont font; 69 private TextureRegion textureRegion; 70 71 /** our box2D world **/ 72 private World world; 73 74 /** our boxes **/ 75 private ArrayList<Body> boxes = new ArrayList<Body>(); 76 77 /** our ground box **/ 78 Body groundBody; 79 80 /** our mouse joint **/ 81 private MouseJoint mouseJoint = null; 82 83 /** a hit body **/ 84 Body hitBody = null; 85 86 @Override create()87 public void create () { 88 // setup the camera. In Box2D we operate on a 89 // meter scale, pixels won't do it. So we use 90 // an orthographic camera with a viewport of 91 // 48 meters in width and 32 meters in height. 92 // We also position the camera so that it 93 // looks at (0,16) (that's where the middle of the 94 // screen will be located). 95 camera = new OrthographicCamera(48, 32); 96 camera.position.set(0, 16, 0); 97 98 // next we setup the immediate mode renderer 99 renderer = new ShapeRenderer(); 100 101 // next we create the box2d debug renderer 102 debugRenderer = new Box2DDebugRenderer(); 103 104 // next we create a SpriteBatch and a font 105 batch = new SpriteBatch(); 106 font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"), false); 107 font.setColor(Color.RED); 108 textureRegion = new TextureRegion(new Texture(Gdx.files.internal("data/badlogicsmall.jpg"))); 109 110 // next we create out physics world. 111 createPhysicsWorld(); 112 113 // register ourselfs as an InputProcessor 114 Gdx.input.setInputProcessor(this); 115 } 116 createPhysicsWorld()117 private void createPhysicsWorld () { 118 // we instantiate a new World with a proper gravity vector 119 // and tell it to sleep when possible. 120 world = new World(new Vector2(0, -10), true); 121 122 float[] vertices = {-0.07421887f, -0.16276085f, -0.12109375f, -0.22786504f, -0.157552f, -0.7122401f, 0.04296875f, 123 -0.7122401f, 0.110677004f, -0.6419276f, 0.13151026f, -0.49869835f, 0.08984375f, -0.3190109f}; 124 125 PolygonShape shape = new PolygonShape(); 126 shape.set(vertices); 127 128 // next we create a static ground platform. This platform 129 // is not moveable and will not react to any influences from 130 // outside. It will however influence other bodies. First we 131 // create a PolygonShape that holds the form of the platform. 132 // it will be 100 meters wide and 2 meters high, centered 133 // around the origin 134 PolygonShape groundPoly = new PolygonShape(); 135 groundPoly.setAsBox(50, 1); 136 137 // next we create the body for the ground platform. It's 138 // simply a static body. 139 BodyDef groundBodyDef = new BodyDef(); 140 groundBodyDef.type = BodyType.StaticBody; 141 groundBody = world.createBody(groundBodyDef); 142 143 // finally we add a fixture to the body using the polygon 144 // defined above. Note that we have to dispose PolygonShapes 145 // and CircleShapes once they are no longer used. This is the 146 // only time you have to care explicitly for memory management. 147 FixtureDef fixtureDef = new FixtureDef(); 148 fixtureDef.shape = groundPoly; 149 fixtureDef.filter.groupIndex = 0; 150 groundBody.createFixture(fixtureDef); 151 groundPoly.dispose(); 152 153 // We also create a simple ChainShape we put above our 154 // ground polygon for extra funkyness. 155 ChainShape chainShape = new ChainShape(); 156 chainShape.createLoop(new Vector2[] {new Vector2(-10, 10), new Vector2(-10, 5), new Vector2(10, 5), new Vector2(10, 11),}); 157 BodyDef chainBodyDef = new BodyDef(); 158 chainBodyDef.type = BodyType.StaticBody; 159 Body chainBody = world.createBody(chainBodyDef); 160 chainBody.createFixture(chainShape, 0); 161 chainShape.dispose(); 162 163 createBoxes(); 164 165 // You can savely ignore the rest of this method :) 166 world.setContactListener(new ContactListener() { 167 @Override 168 public void beginContact (Contact contact) { 169 // System.out.println("begin contact"); 170 } 171 172 @Override 173 public void endContact (Contact contact) { 174 // System.out.println("end contact"); 175 } 176 177 @Override 178 public void preSolve (Contact contact, Manifold oldManifold) { 179 // Manifold.ManifoldType type = oldManifold.getType(); 180 // Vector2 localPoint = oldManifold.getLocalPoint(); 181 // Vector2 localNormal = oldManifold.getLocalNormal(); 182 // int pointCount = oldManifold.getPointCount(); 183 // ManifoldPoint[] points = oldManifold.getPoints(); 184 // System.out.println("pre solve, " + type + 185 // ", point: " + localPoint + 186 // ", local normal: " + localNormal + 187 // ", #points: " + pointCount + 188 // ", [" + points[0] + ", " + points[1] + "]"); 189 } 190 191 @Override 192 public void postSolve (Contact contact, ContactImpulse impulse) { 193 // float[] ni = impulse.getNormalImpulses(); 194 // float[] ti = impulse.getTangentImpulses(); 195 // System.out.println("post solve, normal impulses: " + ni[0] + ", " + ni[1] + ", tangent impulses: " + ti[0] + ", " + ti[1]); 196 } 197 }); 198 } 199 createBoxes()200 private void createBoxes () { 201 // next we create 50 boxes at random locations above the ground 202 // body. First we create a nice polygon representing a box 2 meters 203 // wide and high. 204 PolygonShape boxPoly = new PolygonShape(); 205 boxPoly.setAsBox(1, 1); 206 207 // next we create the 50 box bodies using the PolygonShape we just 208 // defined. This process is similar to the one we used for the ground 209 // body. Note that we reuse the polygon for each body fixture. 210 for (int i = 0; i < 20; i++) { 211 // Create the BodyDef, set a random position above the 212 // ground and create a new body 213 BodyDef boxBodyDef = new BodyDef(); 214 boxBodyDef.type = BodyType.DynamicBody; 215 boxBodyDef.position.x = -24 + (float)(Math.random() * 48); 216 boxBodyDef.position.y = 10 + (float)(Math.random() * 100); 217 Body boxBody = world.createBody(boxBodyDef); 218 219 boxBody.createFixture(boxPoly, 1); 220 221 // add the box to our list of boxes 222 boxes.add(boxBody); 223 } 224 225 // we are done, all that's left is disposing the boxPoly 226 boxPoly.dispose(); 227 } 228 229 @Override render()230 public void render () { 231 // first we update the world. For simplicity 232 // we use the delta time provided by the Graphics 233 // instance. Normally you'll want to fix the time 234 // step. 235 long start = TimeUtils.nanoTime(); 236 world.step(Gdx.graphics.getDeltaTime(), 8, 3); 237 float updateTime = (TimeUtils.nanoTime() - start) / 1000000000.0f; 238 239 // next we clear the color buffer and set the camera 240 // matrices 241 Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 242 camera.update(); 243 244 // next we render the ground body 245 renderBox(groundBody, 50, 1); 246 247 // next we render each box via the SpriteBatch. 248 // for this we have to set the projection matrix of the 249 // spritebatch to the camera's combined matrix. This will 250 // make the spritebatch work in world coordinates 251 batch.getProjectionMatrix().set(camera.combined); 252 batch.begin(); 253 for (int i = 0; i < boxes.size(); i++) { 254 Body box = boxes.get(i); 255 Vector2 position = box.getPosition(); // that's the box's center position 256 float angle = MathUtils.radiansToDegrees * box.getAngle(); // the rotation angle around the center 257 batch.draw(textureRegion, position.x - 1, position.y - 1, // the bottom left corner of the box, unrotated 258 1f, 1f, // the rotation center relative to the bottom left corner of the box 259 2, 2, // the width and height of the box 260 1, 1, // the scale on the x- and y-axis 261 angle); // the rotation angle 262 } 263 batch.end(); 264 265 // next we use the debug renderer. Note that we 266 // simply apply the camera again and then call 267 // the renderer. the camera.apply() call is actually 268 // not needed as the opengl matrices are already set 269 // by the spritebatch which in turn uses the camera matrices :) 270 debugRenderer.render(world, camera.combined); 271 272 // finally we render all contact points 273 renderer.setProjectionMatrix(camera.combined); 274 renderer.begin(ShapeType.Point); 275 renderer.setColor(0, 1, 0, 1); 276 for (int i = 0; i < world.getContactCount(); i++) { 277 Contact contact = world.getContactList().get(i); 278 // we only render the contact if it actually touches 279 if (contact.isTouching()) { 280 // get the world manifold from which we get the 281 // contact points. A manifold can have 0, 1 or 2 282 // contact points. 283 WorldManifold manifold = contact.getWorldManifold(); 284 int numContactPoints = manifold.getNumberOfContactPoints(); 285 for (int j = 0; j < numContactPoints; j++) { 286 Vector2 point = manifold.getPoints()[j]; 287 renderer.point(point.x, point.y, 0); 288 } 289 } 290 } 291 renderer.end(); 292 293 // finally we render the time it took to update the world 294 // for this we have to set the projection matrix again, so 295 // we work in pixel coordinates 296 batch.getProjectionMatrix().setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 297 batch.begin(); 298 font.draw(batch, "fps: " + Gdx.graphics.getFramesPerSecond() + " update time: " + updateTime, 0, 20); 299 batch.end(); 300 } 301 302 Matrix4 transform = new Matrix4(); 303 renderBox(Body body, float halfWidth, float halfHeight)304 private void renderBox (Body body, float halfWidth, float halfHeight) { 305 // get the bodies center and angle in world coordinates 306 Vector2 pos = body.getWorldCenter(); 307 float angle = body.getAngle(); 308 309 // set the translation and rotation matrix 310 transform.setToTranslation(pos.x, pos.y, 0); 311 transform.rotate(0, 0, 1, (float)Math.toDegrees(angle)); 312 313 // render the box 314 renderer.begin(ShapeType.Line); 315 renderer.setTransformMatrix(transform); 316 renderer.setColor(1, 1, 1, 1); 317 renderer.rect(-halfWidth, -halfHeight, halfWidth * 2, halfHeight * 2); 318 renderer.end(); 319 } 320 321 /** we instantiate this vector and the callback here so we don't irritate the GC **/ 322 Vector3 testPoint = new Vector3(); 323 QueryCallback callback = new QueryCallback() { 324 @Override 325 public boolean reportFixture (Fixture fixture) { 326 // if the hit fixture's body is the ground body 327 // we ignore it 328 if (fixture.getBody() == groundBody) return true; 329 330 // if the hit point is inside the fixture of the body 331 // we report it 332 if (fixture.testPoint(testPoint.x, testPoint.y)) { 333 hitBody = fixture.getBody(); 334 return false; 335 } else 336 return true; 337 } 338 }; 339 340 @Override touchDown(int x, int y, int pointer, int newParam)341 public boolean touchDown (int x, int y, int pointer, int newParam) { 342 // translate the mouse coordinates to world coordinates 343 testPoint.set(x, y, 0); 344 camera.unproject(testPoint); 345 346 // ask the world which bodies are within the given 347 // bounding box around the mouse pointer 348 hitBody = null; 349 world.QueryAABB(callback, testPoint.x - 0.1f, testPoint.y - 0.1f, testPoint.x + 0.1f, testPoint.y + 0.1f); 350 351 // if we hit something we create a new mouse joint 352 // and attach it to the hit body. 353 if (hitBody != null) { 354 MouseJointDef def = new MouseJointDef(); 355 def.bodyA = groundBody; 356 def.bodyB = hitBody; 357 def.collideConnected = true; 358 def.target.set(testPoint.x, testPoint.y); 359 def.maxForce = 1000.0f * hitBody.getMass(); 360 361 mouseJoint = (MouseJoint)world.createJoint(def); 362 hitBody.setAwake(true); 363 } else { 364 for (Body box : boxes) 365 world.destroyBody(box); 366 boxes.clear(); 367 createBoxes(); 368 } 369 370 return false; 371 } 372 373 /** another temporary vector **/ 374 Vector2 target = new Vector2(); 375 376 @Override touchDragged(int x, int y, int pointer)377 public boolean touchDragged (int x, int y, int pointer) { 378 // if a mouse joint exists we simply update 379 // the target of the joint based on the new 380 // mouse coordinates 381 if (mouseJoint != null) { 382 camera.unproject(testPoint.set(x, y, 0)); 383 mouseJoint.setTarget(target.set(testPoint.x, testPoint.y)); 384 } 385 return false; 386 } 387 388 @Override touchUp(int x, int y, int pointer, int button)389 public boolean touchUp (int x, int y, int pointer, int button) { 390 // if a mouse joint exists we simply destroy it 391 if (mouseJoint != null) { 392 world.destroyJoint(mouseJoint); 393 mouseJoint = null; 394 } 395 return false; 396 } 397 398 @Override dispose()399 public void dispose () { 400 world.dispose(); 401 renderer.dispose(); 402 debugRenderer.dispose(); 403 font.dispose(); 404 textureRegion.getTexture().dispose(); 405 } 406 } 407