/*******************************************************************************
* 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.graphics.Camera;
import com.badlogic.gdx.math.Frustum;
import com.badlogic.gdx.math.Plane;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.bullet.collision.*;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.Disposable;
import java.nio.FloatBuffer;
/** Performs the occlusion culling or k-DOP culling using the dynamic bounding volume tree from the Bullet broadphase.
* <p>
* Occlusion culling is a process that determines which objects are visible or hidden from the viewpoint of a camera. This is
* achieved by rendering the bounding boxes of collision objects to a depth buffer using a software rasterizer. When determining
* if an object is visible to the camera, this depth buffer is queried and compared against the depth of the object in question.
* <p>
* k-DOP culling determines which objects are inside a camera frustum. The process is accelerated by the dynamic bounding volume
* tree.
*
* @author jsjolund */
public abstract class OcclusionCuller implements Disposable {
protected class Collider extends ICollide {
/** Callback method for {@link btDbvt#collideKDOP}. The bounding volume tree node in the parameter is inside the camera
* frustum, as are any collision objects it contains.
*
* @param node A bounding volume tree node, the children of which are all inside the camera frustum. */
@Override
public boolean AllLeaves (btDbvtNode node) {
if (node.isleaf()) {
onObjectVisible(node.getDataAsProxyClientObject());
} else {
long nodePointer = node.getCPointer();
btDbvtNode child;
if ((child = btDbvtNode.internalTemp(nodePointer, false).getChild(0)).getCPointer() != 0) AllLeaves(child);
if ((child = btDbvtNode.internalTemp(nodePointer, false).getChild(1)).getCPointer() != 0) AllLeaves(child);
}
return true;
}
/** Callback for {@link btDbvt#collideOCL}. This method must check if the input node from the broadphase bounding volume
* tree is completely occluded by an occluder node previously rendered by {@link #Process(btDbvtNode, float)}.
* <p>
* The node may or may not be a leaf node, i.e. may or may not contain a single collision object. If the node is not a leaf
* (i.e. an internal node), and the node is occluded, the child nodes it contains will not be processed or checked for
* occlusion, since they are contained in this parent node bounding volume and must therefore also be occluded.
*
* @param node A node from the broadphase bounding volume tree.
* @return False if the node is completely occluded, true otherwise. */
@Override
public boolean Descent (btDbvtNode node) {
return oclBuffer.queryAABB(tmpV1.set(node.getVolume().Center()), tmpV2.set(node.getVolume().Extents()));
}
/** Callback for {@link btDbvt#collideOCL}, which will trigger a call to this method if a leaf node is not occluded. If the
* node contains an object which can occlude others, it will be added to the depth buffer so that it may be considered in
* future occlusion checks.
* <p>
* Only box shaped occluder objects are supported.
*
* @param leaf A leaf node which contains a collision object.
* @param depth The depth of the node along the sorting axis. Objects closer to the camera will have a value closer to
* zero. */
@Override
public void Process (btDbvtNode leaf, float depth) {
btCollisionObject object = leaf.getDataAsProxyClientObject();
onObjectVisible(object);
btCollisionShape shape = object.getCollisionShape();
if (shape instanceof btBoxShape && isOccluder(object)) {
oclBuffer.drawBB(object.getWorldTransform(), ((btBoxShape)shape).getHalfExtentsWithMargin());
}
}
}
private static final int NUM_PLANES = 5;
private final FloatBuffer frustumNormals = BufferUtils.newFloatBuffer(NUM_PLANES * 4);
private final FloatBuffer frustumOffsets = BufferUtils.newFloatBuffer(NUM_PLANES);
final Vector3 tmpV1 = new Vector3();
final Vector3 tmpV2 = new Vector3();
OcclusionBuffer oclBuffer;
private final Collider collider = new Collider();
@Override
public void dispose () {
collider.dispose();
oclBuffer = null;
}
/** True if this collision object can block vision of other collision objects. If true, its collision shape will be drawn to
* the depth buffer and considered in future occlusion checks. Only btBoxShape collision shapes can be occluders, other types
* of shapes will be ignored. However, box occluders can block vision of any type of collision shape.
*
* @param object Object to check
* @return True if the collision object can occlude other objects */
public abstract boolean isOccluder (btCollisionObject object);
/** When performing occlusion culling, this method is a callback for when a collision object is found to be visible from the
* point of view of the camera.
* <p>
* When performing k-DOP culling, this method is a callback for when a collision object is found to be inside the camera
* frustum (regardless of occlusion).
*
* @param object A collision object which is visible to the camera */
public abstract void onObjectVisible (btCollisionObject object);
/** Performs k-DOP (k-Discrete Oriented Polytope) culling using the Bullet method {@link btDbvt#collideKDOP}. Finds all
* collision objects inside the camera frustum, each of which will trigger a callback to
* {@link #onObjectVisible(btCollisionObject)}. The process of finding objects in frustum is accelerated by the broadphase
* dynamic bounding volume tree ({@link btDbvt}).
*
* @param broadphase The dynamics world broadphase
* @param camera Camera for which to perform k-DOP culling */
public void performKDOPCulling (btDbvtBroadphase broadphase, Camera camera) {
setFrustumPlanes(camera.frustum);
btDbvt.collideKDOP(broadphase.getSet1().getRoot(), frustumNormals, frustumOffsets, NUM_PLANES, collider);
btDbvt.collideKDOP(broadphase.getSet0().getRoot(), frustumNormals, frustumOffsets, NUM_PLANES, collider);
}
/** Performs occlusion culling using the Bullet method {@link btDbvt#collideOCL}. Finds all collision objects which are visible
* to the camera, where vision is not blocked (occluded) by another object. If a collision object from the broadphase is
* visible to the camera, a callback is made to {@link #onObjectVisible(btCollisionObject)}. Only collision objects for which
* {@link #isOccluder(btCollisionObject)} returns true can occlude others. These collision objects must have a
* {@link btBoxShape} collision shape. However, box occluders can block vision of any type of shape.
*
* @param broadphase The dynamics world broadphase
* @param oclBuffer The occlusion buffer in which to query and draw depth during culling
* @param camera Camera for which to perform occlusion culling */
public void performOcclusionCulling (btDbvtBroadphase broadphase, OcclusionBuffer oclBuffer, Camera camera) {
this.oclBuffer = oclBuffer;
oclBuffer.setProjectionMatrix(camera.combined);
setFrustumPlanes(camera.frustum);
btDbvt.collideOCL(broadphase.getSet1().getRoot(), frustumNormals, frustumOffsets, camera.direction, NUM_PLANES, collider);
btDbvt.collideOCL(broadphase.getSet0().getRoot(), frustumNormals, frustumOffsets, camera.direction, NUM_PLANES, collider);
}
/** @param frustum Set the frustum plane buffers to this frustum */
private void setFrustumPlanes (Frustum frustum) {
// All frustum planes except 'near' (index 0) should be sent to Bullet.
frustumNormals.clear();
frustumOffsets.clear();
for (int i = 1; i < 6; i++) {
Plane plane = frustum.planes[i];
// Since the plane normals map to an array of btVector3, all four vector components (x, y, z, w)
// required by the C++ struct must be provided. The plane offset from origin (d) must also be set.
frustumNormals.put(plane.normal.x);
frustumNormals.put(plane.normal.y);
frustumNormals.put(plane.normal.z);
frustumNormals.put(0);
frustumOffsets.put(plane.d);
}
}
}