• 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.bullet;
18 
19 import com.badlogic.gdx.Gdx;
20 import com.badlogic.gdx.graphics.Color;
21 import com.badlogic.gdx.graphics.GL20;
22 import com.badlogic.gdx.graphics.PerspectiveCamera;
23 import com.badlogic.gdx.graphics.VertexAttribute;
24 import com.badlogic.gdx.graphics.VertexAttributes.Usage;
25 import com.badlogic.gdx.graphics.g3d.Material;
26 import com.badlogic.gdx.graphics.g3d.Model;
27 import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
28 import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
29 import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
30 import com.badlogic.gdx.math.Matrix4;
31 import com.badlogic.gdx.math.Vector3;
32 import com.badlogic.gdx.physics.bullet.collision.btBroadphasePairArray;
33 import com.badlogic.gdx.physics.bullet.collision.btCollisionDispatcher;
34 import com.badlogic.gdx.physics.bullet.collision.btCollisionObject;
35 import com.badlogic.gdx.physics.bullet.collision.btCollisionShape;
36 import com.badlogic.gdx.physics.bullet.collision.btCollisionWorld;
37 import com.badlogic.gdx.physics.bullet.collision.btCompoundShape;
38 import com.badlogic.gdx.physics.bullet.collision.btConvexHullShape;
39 import com.badlogic.gdx.physics.bullet.collision.btDbvtBroadphase;
40 import com.badlogic.gdx.physics.bullet.collision.btDefaultCollisionConfiguration;
41 import com.badlogic.gdx.physics.bullet.collision.btPairCachingGhostObject;
42 import com.badlogic.gdx.physics.bullet.collision.btPersistentManifoldArray;
43 import com.badlogic.gdx.utils.Array;
44 
45 /** @author Xoppa */
46 public class FrustumCullingTest extends BaseBulletTest {
47 	/** Only show entities inside the frustum */
48 	final static int CULL_FRUSTUM = 1;
49 	/** Transform the render cam with the frustum */
50 	final static int FRUSTUM_CAM = 2;
51 
52 	final static boolean USE_BULLET_FRUSTUM_CULLING = true;
53 
54 	int state = 0; // 0 = No culling, look from above
55 
56 	final static int BOXCOUNT = 200;
57 
58 	final static float BOX_X_MIN = -25;
59 	final static float BOX_Y_MIN = -25;
60 	final static float BOX_Z_MIN = -25;
61 
62 	final static float BOX_X_MAX = 25;
63 	final static float BOX_Y_MAX = 25;
64 	final static float BOX_Z_MAX = 25;
65 
66 	final static float SPEED_X = 360f / 7f;
67 	final static float SPEED_Y = 360f / 19f;
68 	final static float SPEED_Z = 360f / 13f;
69 
70 	final static Vector3 tmpV = new Vector3();
71 	final static Matrix4 tmpM = new Matrix4();
72 
73 	final static int ptrs[] = new int[512];
74 	final static Array<btCollisionObject> visibleObjects = new Array<btCollisionObject>();
75 
createFrustumObject(final Vector3... points)76 	public static btPairCachingGhostObject createFrustumObject (final Vector3... points) {
77 		final btPairCachingGhostObject result = new TestPairCachingGhostObject();
78 		final boolean USE_COMPOUND = true;
79 		// Using a compound shape is not necessary, but it's good practice to create shapes around the center.
80 		if (USE_COMPOUND) {
81 			final Vector3 centerNear = new Vector3(points[2]).sub(points[0]).scl(0.5f).add(points[0]);
82 			final Vector3 centerFar = new Vector3(points[6]).sub(points[4]).scl(0.5f).add(points[4]);
83 			final Vector3 center = new Vector3(centerFar).sub(centerNear).scl(0.5f).add(centerNear);
84 			final btConvexHullShape hullShape = new btConvexHullShape();
85 			for (int i = 0; i < points.length; i++)
86 				hullShape.addPoint(tmpV.set(points[i]).sub(center));
87 			final btCompoundShape shape = new btCompoundShape();
88 			shape.addChildShape(tmpM.setToTranslation(center), hullShape);
89 			result.setCollisionShape(shape);
90 		} else {
91 			final btConvexHullShape shape = new btConvexHullShape();
92 			for (int i = 0; i < points.length; i++)
93 				shape.addPoint(points[i]);
94 			result.setCollisionShape(shape);
95 		}
96 		result.setCollisionFlags(btCollisionObject.CollisionFlags.CF_NO_CONTACT_RESPONSE);
97 		return result;
98 	}
99 
getEntitiesCollidingWithObject(final BulletWorld world, final btCollisionObject object, final Array<BulletEntity> out, final btPersistentManifoldArray tmpArr)100 	public static Array<BulletEntity> getEntitiesCollidingWithObject (final BulletWorld world, final btCollisionObject object,
101 		final Array<BulletEntity> out, final btPersistentManifoldArray tmpArr) {
102 		// Fetch the array of contacts
103 		btBroadphasePairArray arr = world.broadphase.getOverlappingPairCache().getOverlappingPairArray();
104 		// Get the user values (which are indices in the entities array) of all objects colliding with the object
105 		final int n = arr.getCollisionObjectsValue(ptrs, object);
106 		// Fill the array of entities
107 		out.clear();
108 		for (int i = 0; i < n; i++)
109 			out.add(world.entities.get(ptrs[i]));
110 		return out;
111 	}
112 
createFrustumModel(final Vector3... p)113 	public static Model createFrustumModel (final Vector3... p) {
114 		ModelBuilder builder = new ModelBuilder();
115 		builder.begin();
116 		MeshPartBuilder mpb = builder.part("", GL20.GL_LINES, Usage.Position | Usage.Normal, new Material(new ColorAttribute(ColorAttribute.Diffuse, Color.WHITE)));
117 		mpb.vertex(p[0].x, p[0].y, p[0].z, 0, 0, 1, p[1].x, p[1].y, p[1].z, 0, 0, 1, p[2].x,
118 			p[2].y, p[2].z, 0, 0, 1, p[3].x, p[3].y, p[3].z, 0, 0,
119 			1, // near
120 			p[4].x, p[4].y, p[4].z, 0, 0, -1, p[5].x, p[5].y, p[5].z, 0, 0, -1, p[6].x, p[6].y, p[6].z, 0, 0, -1, p[7].x, p[7].y,
121 			p[7].z, 0, 0, -1);
122 		mpb.index((short)0, (short)1, (short)1, (short)2, (short)2, (short)3, (short)3, (short)0);
123 		mpb.index((short)4, (short)5, (short)5, (short)6, (short)6, (short)7, (short)7, (short)4);
124 		mpb.index((short)0, (short)4, (short)1, (short)5, (short)2, (short)6, (short)3, (short)7);
125 		return builder.end();
126 	}
127 
128 	private float angleX, angleY, angleZ;
129 	private btPairCachingGhostObject frustumObject;
130 	private BulletEntity frustumEntity;
131 	private final Array<BulletEntity> visibleEntities = new Array<BulletEntity>();
132 	private btPersistentManifoldArray tempManifoldArr;
133 	private PerspectiveCamera frustumCam;
134 	private PerspectiveCamera overviewCam;
135 
136 	@Override
create()137 	public void create () {
138 		super.create();
139 
140 		instructions = "Tap to toggle view\nLong press to toggle debug mode\nSwipe for next test\nCtrl+drag to rotate\nScroll to zoom";
141 
142 		tempManifoldArr = new btPersistentManifoldArray();
143 
144 		world.addConstructor("collisionBox", new BulletConstructor(world.getConstructor("box").model));
145 
146 		// Create the entities
147 		final float dX = BOX_X_MAX - BOX_X_MIN;
148 		final float dY = BOX_Y_MAX - BOX_Y_MIN;
149 		final float dZ = BOX_Z_MAX - BOX_Z_MIN;
150 		for (int i = 0; i < BOXCOUNT; i++)
151 			world.add("collisionBox", BOX_X_MIN + dX * (float)Math.random(), BOX_Y_MIN + dY * (float)Math.random(),
152 				BOX_Z_MIN + dZ * (float)Math.random()).setColor(Color.GRAY);
153 
154 		frustumCam = new PerspectiveCamera(camera.fieldOfView, camera.viewportWidth, camera.viewportHeight);
155 		frustumCam.far = Vector3.len(BOX_X_MAX, BOX_Y_MAX, BOX_Z_MAX);
156 		frustumCam.update();
157 
158 		overviewCam = camera;
159 		overviewCam.position.set(BOX_X_MAX, BOX_Y_MAX, BOX_Z_MAX);
160 		overviewCam.lookAt(Vector3.Zero);
161 		overviewCam.far = 150f;
162 		overviewCam.update();
163 
164 		final Model frustumModel = createFrustumModel(frustumCam.frustum.planePoints);
165 		disposables.add(frustumModel);
166 		frustumObject = createFrustumObject(frustumCam.frustum.planePoints);
167 		world.add(frustumEntity = new BulletEntity(frustumModel, frustumObject, 0, 0, 0));
168 		frustumEntity.setColor(Color.BLUE);
169 	}
170 
171 	@Override
createWorld()172 	public BulletWorld createWorld () {
173 		// No need to use dynamics for this test
174 		btDbvtBroadphase broadphase = new btDbvtBroadphase();
175 		btDefaultCollisionConfiguration collisionConfig = new btDefaultCollisionConfiguration();
176 		btCollisionDispatcher dispatcher = new btCollisionDispatcher(collisionConfig);
177 		btCollisionWorld collisionWorld = new btCollisionWorld(dispatcher, broadphase, collisionConfig);
178 		return new BulletWorld(collisionConfig, dispatcher, broadphase, null, collisionWorld);
179 	}
180 
181 	@Override
update()182 	public void update () {
183 		super.update();
184 		// Not using dynamics, so update the collision world manually
185 		if (USE_BULLET_FRUSTUM_CULLING) {
186 			if (world.performanceCounter != null) world.performanceCounter.start();
187 			world.collisionWorld.performDiscreteCollisionDetection();
188 			if (world.performanceCounter != null) world.performanceCounter.stop();
189 		}
190 	}
191 
192 	@Override
render()193 	public void render () {
194 		final float dt = Gdx.graphics.getDeltaTime();
195 		frustumEntity.transform.idt();
196 		frustumEntity.transform.rotate(Vector3.X, angleX = (angleX + dt * SPEED_X) % 360);
197 		frustumEntity.transform.rotate(Vector3.Y, angleY = (angleY + dt * SPEED_Y) % 360);
198 		frustumEntity.transform.rotate(Vector3.Z, angleZ = (angleZ + dt * SPEED_Z) % 360);
199 
200 		// Transform the ghost object
201 		frustumEntity.body.setWorldTransform(frustumEntity.transform);
202 		// Transform the frustum cam
203 		frustumCam.direction.set(0, 0, -1);
204 		frustumCam.up.set(0, 1, 0);
205 		frustumCam.position.set(0, 0, 0);
206 		frustumCam.rotate(frustumEntity.transform);
207 		frustumCam.update();
208 
209 		super.render();
210 
211 		performance.append(" visible: ").append(visibleEntities.size);
212 	}
213 
214 	@Override
renderWorld()215 	protected void renderWorld () {
216 		if (world.performanceCounter != null) world.performanceCounter.start();
217 		if (USE_BULLET_FRUSTUM_CULLING)
218 			getEntitiesCollidingWithObject(world, frustumObject, visibleEntities, tempManifoldArr);
219 		else {
220 			visibleEntities.clear();
221 			for (int i = 0; i < world.entities.size; i++) {
222 				final BulletEntity e = world.entities.get(i);
223 				if (e == frustumEntity) continue;
224 				e.modelInstance.transform.getTranslation(tmpV);
225 				if (frustumCam.frustum.sphereInFrustum(tmpV, 1)) visibleEntities.add(e);
226 			}
227 		}
228 		if (world.performanceCounter != null) world.performanceCounter.stop();
229 
230 		for (int i = 0; i < visibleEntities.size; i++)
231 			visibleEntities.get(i).setColor(Color.RED);
232 
233 		modelBatch.begin(camera);
234 		if ((state & CULL_FRUSTUM) == CULL_FRUSTUM) {
235 			world.render(modelBatch, environment, visibleEntities);
236 			world.render(modelBatch, environment, frustumEntity);
237 		} else
238 			world.render(modelBatch, environment);
239 		modelBatch.end();
240 
241 		for (int i = 0; i < visibleEntities.size; i++)
242 			visibleEntities.get(i).setColor(Color.GRAY);
243 	}
244 
245 	@Override
beginRender(boolean lighting)246 	protected void beginRender (boolean lighting) {
247 		super.beginRender(false);
248 	}
249 
250 	@Override
dispose()251 	public void dispose () {
252 		frustumObject = null;
253 
254 		super.dispose();
255 
256 		if (tempManifoldArr != null) tempManifoldArr.dispose();
257 		tempManifoldArr = null;
258 	}
259 
260 	@Override
tap(float x, float y, int count, int button)261 	public boolean tap (float x, float y, int count, int button) {
262 		state = (state + 1) % 3;
263 		if ((state & FRUSTUM_CAM) == FRUSTUM_CAM)
264 			camera = frustumCam;
265 		else
266 			camera = overviewCam;
267 		return true;
268 	}
269 
270 	// Simple helper class to keep a reference to the collision shape
271 	public static class TestPairCachingGhostObject extends btPairCachingGhostObject {
272 		public btCollisionShape shape;
273 
274 		@Override
setCollisionShape(btCollisionShape collisionShape)275 		public void setCollisionShape (btCollisionShape collisionShape) {
276 			shape = collisionShape;
277 			super.setCollisionShape(collisionShape);
278 		}
279 
280 		@Override
dispose()281 		public void dispose () {
282 			super.dispose();
283 			if (shape != null) shape.dispose();
284 			shape = null;
285 		}
286 	}
287 }
288