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
17package com.badlogic.gdx.tests.bullet;
18
19import com.badlogic.gdx.Gdx;
20import com.badlogic.gdx.graphics.Color;
21import com.badlogic.gdx.graphics.PerspectiveCamera;
22import com.badlogic.gdx.graphics.g3d.Model;
23import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
24import com.badlogic.gdx.math.Vector3;
25import com.badlogic.gdx.physics.bullet.collision.*;
26
27/** Based on FrustumCullingTest by Xoppa.
28 *
29 * @author jsjolund */
30public class PairCacheTest extends BaseBulletTest {
31
32	final static float BOX_X_MIN = -25;
33	final static float BOX_Y_MIN = -25;
34	final static float BOX_Z_MIN = -25;
35
36	final static float BOX_X_MAX = 25;
37	final static float BOX_Y_MAX = 25;
38	final static float BOX_Z_MAX = 25;
39
40	final static float SPEED_X = 360f / 7f;
41	final static float SPEED_Y = 360f / 19f;
42	final static float SPEED_Z = 360f / 13f;
43
44	final static int BOXCOUNT = 100;
45
46	private boolean useFrustumCam = false;
47
48	private btPairCachingGhostObject ghostObject;
49	private BulletEntity ghostEntity;
50	private btPersistentManifoldArray manifoldArray;
51
52	private float angleX, angleY, angleZ;
53
54	private ShapeRenderer shapeRenderer;
55
56	private PerspectiveCamera frustumCam;
57	private PerspectiveCamera overviewCam;
58
59	@Override
60	public void create () {
61		super.create();
62
63		instructions = "Tap to toggle view\nLong press to toggle debug mode\nSwipe for next test\nCtrl+drag to rotate\nScroll to zoom";
64
65		world.addConstructor("collisionBox", new BulletConstructor(world.getConstructor("box").model));
66
67		// Create the entities
68		final float dX = BOX_X_MAX - BOX_X_MIN;
69		final float dY = BOX_Y_MAX - BOX_Y_MIN;
70		final float dZ = BOX_Z_MAX - BOX_Z_MIN;
71		for (int i = 0; i < BOXCOUNT; i++)
72			world.add("collisionBox", BOX_X_MIN + dX * (float)Math.random(), BOX_Y_MIN + dY * (float)Math.random(),
73					BOX_Z_MIN + dZ * (float)Math.random()).setColor(0.25f + 0.5f * (float)Math.random(),
74					0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 1f);
75
76		manifoldArray = new btPersistentManifoldArray();
77		disposables.add(manifoldArray);
78
79		overviewCam = camera;
80		overviewCam.position.set(BOX_X_MAX, BOX_Y_MAX, BOX_Z_MAX);
81		overviewCam.lookAt(Vector3.Zero);
82		overviewCam.far = 150f;
83		overviewCam.update();
84
85		frustumCam = new PerspectiveCamera(camera.fieldOfView, camera.viewportWidth, camera.viewportHeight);
86		frustumCam.far = Vector3.len(BOX_X_MAX, BOX_Y_MAX, BOX_Z_MAX);
87		frustumCam.update();
88
89		final Model ghostModel = FrustumCullingTest.createFrustumModel(frustumCam.frustum.planePoints);
90		disposables.add(ghostModel);
91
92		// The ghost object does not need to be shaped as a camera frustum, it can have any collision shape.
93		ghostObject = FrustumCullingTest.createFrustumObject(frustumCam.frustum.planePoints);
94		disposables.add(ghostObject);
95
96		world.add(ghostEntity = new BulletEntity(ghostModel, ghostObject, 0, 0, 0));
97		disposables.add(ghostEntity);
98
99		shapeRenderer = new ShapeRenderer();
100		disposables.add(shapeRenderer);
101
102	}
103
104	@Override
105	public BulletWorld createWorld () {
106		// No need to use dynamics for this test
107		btDbvtBroadphase broadphase = new btDbvtBroadphase();
108		btDefaultCollisionConfiguration collisionConfig = new btDefaultCollisionConfiguration();
109		btCollisionDispatcher dispatcher = new btCollisionDispatcher(collisionConfig);
110		btCollisionWorld collisionWorld = new btCollisionWorld(dispatcher, broadphase, collisionConfig);
111		return new BulletWorld(collisionConfig, dispatcher, broadphase, null, collisionWorld);
112	}
113
114	@Override
115	public void render () {
116		final float dt = Gdx.graphics.getDeltaTime();
117		ghostEntity.transform.idt();
118		ghostEntity.transform.rotate(Vector3.X, angleX = (angleX + dt * SPEED_X) % 360);
119		ghostEntity.transform.rotate(Vector3.Y, angleY = (angleY + dt * SPEED_Y) % 360);
120		ghostEntity.transform.rotate(Vector3.Z, angleZ = (angleZ + dt * SPEED_Z) % 360);
121
122		// Transform the ghost object
123		ghostEntity.body.setWorldTransform(ghostEntity.transform);
124
125		// Transform the frustum cam
126		frustumCam.direction.set(0, 0, -1);
127		frustumCam.up.set(0, 1, 0);
128		frustumCam.position.set(0, 0, 0);
129		frustumCam.rotate(ghostEntity.transform);
130		frustumCam.update();
131
132		super.render();
133
134		// Find all overlapping pairs which contain the ghost object and draw lines between the collision points.
135		shapeRenderer.setProjectionMatrix(camera.combined);
136		shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
137		shapeRenderer.setColor(Color.WHITE);
138
139		btBroadphasePairArray arr = world.broadphase.getOverlappingPairCache().getOverlappingPairArray();
140		int numPairs = arr.size();
141		for (int i = 0; i < numPairs; ++i) {
142			manifoldArray.clear();
143
144			btBroadphasePair pair = arr.at(i);
145			btBroadphaseProxy proxy0 = btBroadphaseProxy.obtain(pair.getPProxy0().getCPointer(), false);
146			btBroadphaseProxy proxy1 = btBroadphaseProxy.obtain(pair.getPProxy1().getCPointer(), false);
147
148			btBroadphasePair collisionPair = world.collisionWorld.getPairCache().findPair(proxy0, proxy1);
149
150			if (collisionPair == null) continue;
151
152			btCollisionAlgorithm algorithm = collisionPair.getAlgorithm();
153			if (algorithm != null) algorithm.getAllContactManifolds(manifoldArray);
154
155			for (int j = 0; j < manifoldArray.size(); j++) {
156				btPersistentManifold manifold = manifoldArray.at(j);
157
158				boolean isFirstBody = manifold.getBody0() == ghostObject;
159				int otherObjectIndex = isFirstBody ? manifold.getBody1().getUserValue() : manifold.getBody0().getUserValue();
160				Color otherObjectColor = world.entities.get(otherObjectIndex).getColor();
161
162				for (int p = 0; p < manifold.getNumContacts(); ++p) {
163					btManifoldPoint pt = manifold.getContactPoint(p);
164
165					if (pt.getDistance() < 0.f) {
166						if (isFirstBody) {
167							pt.getPositionWorldOnA(tmpV2);
168							pt.getPositionWorldOnB(tmpV1);
169						} else {
170							pt.getPositionWorldOnA(tmpV1);
171							pt.getPositionWorldOnB(tmpV2);
172						}
173						shapeRenderer.line(tmpV1.x, tmpV1.y, tmpV1.z, tmpV2.x, tmpV2.y, tmpV2.z, otherObjectColor, Color.WHITE);
174					}
175				}
176			}
177			btBroadphaseProxy.free(proxy0);
178			btBroadphaseProxy.free(proxy1);
179		}
180		shapeRenderer.end();
181	}
182
183	@Override
184	public boolean tap (float x, float y, int count, int button) {
185		useFrustumCam = !useFrustumCam;
186		if (useFrustumCam)
187			camera = frustumCam;
188		else
189			camera = overviewCam;
190		return true;
191	}
192
193	@Override
194	public void update () {
195		super.update();
196		// Not using dynamics, so update the collision world manually
197		world.collisionWorld.performDiscreteCollisionDetection();
198	}
199}
200