/*******************************************************************************
* 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.bullet;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.bullet.collision.*;
/** Based on FrustumCullingTest by Xoppa.
*
* @author jsjolund */
public class PairCacheTest extends BaseBulletTest {
final static float BOX_X_MIN = -25;
final static float BOX_Y_MIN = -25;
final static float BOX_Z_MIN = -25;
final static float BOX_X_MAX = 25;
final static float BOX_Y_MAX = 25;
final static float BOX_Z_MAX = 25;
final static float SPEED_X = 360f / 7f;
final static float SPEED_Y = 360f / 19f;
final static float SPEED_Z = 360f / 13f;
final static int BOXCOUNT = 100;
private boolean useFrustumCam = false;
private btPairCachingGhostObject ghostObject;
private BulletEntity ghostEntity;
private btPersistentManifoldArray manifoldArray;
private float angleX, angleY, angleZ;
private ShapeRenderer shapeRenderer;
private PerspectiveCamera frustumCam;
private PerspectiveCamera overviewCam;
@Override
public void create () {
super.create();
instructions = "Tap to toggle view\nLong press to toggle debug mode\nSwipe for next test\nCtrl+drag to rotate\nScroll to zoom";
world.addConstructor("collisionBox", new BulletConstructor(world.getConstructor("box").model));
// Create the entities
final float dX = BOX_X_MAX - BOX_X_MIN;
final float dY = BOX_Y_MAX - BOX_Y_MIN;
final float dZ = BOX_Z_MAX - BOX_Z_MIN;
for (int i = 0; i < BOXCOUNT; i++)
world.add("collisionBox", BOX_X_MIN + dX * (float)Math.random(), BOX_Y_MIN + dY * (float)Math.random(),
BOX_Z_MIN + dZ * (float)Math.random()).setColor(0.25f + 0.5f * (float)Math.random(),
0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 1f);
manifoldArray = new btPersistentManifoldArray();
disposables.add(manifoldArray);
overviewCam = camera;
overviewCam.position.set(BOX_X_MAX, BOX_Y_MAX, BOX_Z_MAX);
overviewCam.lookAt(Vector3.Zero);
overviewCam.far = 150f;
overviewCam.update();
frustumCam = new PerspectiveCamera(camera.fieldOfView, camera.viewportWidth, camera.viewportHeight);
frustumCam.far = Vector3.len(BOX_X_MAX, BOX_Y_MAX, BOX_Z_MAX);
frustumCam.update();
final Model ghostModel = FrustumCullingTest.createFrustumModel(frustumCam.frustum.planePoints);
disposables.add(ghostModel);
// The ghost object does not need to be shaped as a camera frustum, it can have any collision shape.
ghostObject = FrustumCullingTest.createFrustumObject(frustumCam.frustum.planePoints);
disposables.add(ghostObject);
world.add(ghostEntity = new BulletEntity(ghostModel, ghostObject, 0, 0, 0));
disposables.add(ghostEntity);
shapeRenderer = new ShapeRenderer();
disposables.add(shapeRenderer);
}
@Override
public BulletWorld createWorld () {
// No need to use dynamics for this test
btDbvtBroadphase broadphase = new btDbvtBroadphase();
btDefaultCollisionConfiguration collisionConfig = new btDefaultCollisionConfiguration();
btCollisionDispatcher dispatcher = new btCollisionDispatcher(collisionConfig);
btCollisionWorld collisionWorld = new btCollisionWorld(dispatcher, broadphase, collisionConfig);
return new BulletWorld(collisionConfig, dispatcher, broadphase, null, collisionWorld);
}
@Override
public void render () {
final float dt = Gdx.graphics.getDeltaTime();
ghostEntity.transform.idt();
ghostEntity.transform.rotate(Vector3.X, angleX = (angleX + dt * SPEED_X) % 360);
ghostEntity.transform.rotate(Vector3.Y, angleY = (angleY + dt * SPEED_Y) % 360);
ghostEntity.transform.rotate(Vector3.Z, angleZ = (angleZ + dt * SPEED_Z) % 360);
// Transform the ghost object
ghostEntity.body.setWorldTransform(ghostEntity.transform);
// Transform the frustum cam
frustumCam.direction.set(0, 0, -1);
frustumCam.up.set(0, 1, 0);
frustumCam.position.set(0, 0, 0);
frustumCam.rotate(ghostEntity.transform);
frustumCam.update();
super.render();
// Find all overlapping pairs which contain the ghost object and draw lines between the collision points.
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
shapeRenderer.setColor(Color.WHITE);
btBroadphasePairArray arr = world.broadphase.getOverlappingPairCache().getOverlappingPairArray();
int numPairs = arr.size();
for (int i = 0; i < numPairs; ++i) {
manifoldArray.clear();
btBroadphasePair pair = arr.at(i);
btBroadphaseProxy proxy0 = btBroadphaseProxy.obtain(pair.getPProxy0().getCPointer(), false);
btBroadphaseProxy proxy1 = btBroadphaseProxy.obtain(pair.getPProxy1().getCPointer(), false);
btBroadphasePair collisionPair = world.collisionWorld.getPairCache().findPair(proxy0, proxy1);
if (collisionPair == null) continue;
btCollisionAlgorithm algorithm = collisionPair.getAlgorithm();
if (algorithm != null) algorithm.getAllContactManifolds(manifoldArray);
for (int j = 0; j < manifoldArray.size(); j++) {
btPersistentManifold manifold = manifoldArray.at(j);
boolean isFirstBody = manifold.getBody0() == ghostObject;
int otherObjectIndex = isFirstBody ? manifold.getBody1().getUserValue() : manifold.getBody0().getUserValue();
Color otherObjectColor = world.entities.get(otherObjectIndex).getColor();
for (int p = 0; p < manifold.getNumContacts(); ++p) {
btManifoldPoint pt = manifold.getContactPoint(p);
if (pt.getDistance() < 0.f) {
if (isFirstBody) {
pt.getPositionWorldOnA(tmpV2);
pt.getPositionWorldOnB(tmpV1);
} else {
pt.getPositionWorldOnA(tmpV1);
pt.getPositionWorldOnB(tmpV2);
}
shapeRenderer.line(tmpV1.x, tmpV1.y, tmpV1.z, tmpV2.x, tmpV2.y, tmpV2.z, otherObjectColor, Color.WHITE);
}
}
}
btBroadphaseProxy.free(proxy0);
btBroadphaseProxy.free(proxy1);
}
shapeRenderer.end();
}
@Override
public boolean tap (float x, float y, int count, int button) {
useFrustumCam = !useFrustumCam;
if (useFrustumCam)
camera = frustumCam;
else
camera = overviewCam;
return true;
}
@Override
public void update () {
super.update();
// Not using dynamics, so update the collision world manually
world.collisionWorld.performDiscreteCollisionDetection();
}
}