• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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