/*******************************************************************************
* 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 java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.Ray;
import com.badlogic.gdx.physics.bullet.collision.btBvhTriangleMeshShape;
import com.badlogic.gdx.physics.bullet.collision.btTriangleIndexVertexArray;
import com.badlogic.gdx.physics.bullet.collision.btTriangleRaycastCallback;
import com.badlogic.gdx.physics.bullet.collision.btTriangleRaycastCallback.EFlags;
import com.badlogic.gdx.physics.bullet.linearmath.btVector3;
/** @author jsjolund */
public class TriangleRaycastTest extends BaseBulletTest {
private class MyTriangleRaycastCallback extends btTriangleRaycastCallback {
public Vector3 hitNormalLocal = new Vector3();
public float hitFraction = 1;
public int partId = -1;
public int triangleIndex = -1;
private btVector3 tmpSetFrom = new btVector3();
private btVector3 tmpSetTo = new btVector3();
public MyTriangleRaycastCallback (Vector3 from, Vector3 to) {
super(from, to);
}
public void clearReport () {
hitNormalLocal.setZero();
hitFraction = 1;
partId = -1;
triangleIndex = -1;
}
@Override
public void setHitFraction (float hitFraction) {
super.setHitFraction(hitFraction);
this.hitFraction = hitFraction;
}
@Override
public float reportHit (Vector3 hitNormalLocal, float hitFraction, int partId, int triangleIndex) {
// The hit with lowest hitFraction is closest to the ray origin.
// We need to find the lowest hitFraction since the super class does not handle it for us.
if (hitFraction < this.hitFraction) {
this.hitNormalLocal.set(hitNormalLocal);
this.hitFraction = hitFraction;
this.partId = partId;
this.triangleIndex = triangleIndex;
}
return hitFraction;
}
public void setFrom (Vector3 value) {
tmpSetFrom.setValue(value.x, value.y, value.z);
super.setFrom(tmpSetFrom);
}
public void setTo (Vector3 value) {
tmpSetTo.setValue(value.x, value.y, value.z);
super.setTo(tmpSetTo);
}
@Override
public void dispose () {
tmpSetFrom.dispose();
tmpSetTo.dispose();
super.dispose();
}
}
private Model model;
private btBvhTriangleMeshShape triangleShape;
private MyTriangleRaycastCallback triangleRaycastCallback;
private Vector3[] selectedTriangleVertices = {new Vector3(), new Vector3(), new Vector3()};
private ShapeRenderer shapeRenderer;
private Vector3 rayFrom = new Vector3();
private Vector3 rayTo = new Vector3();
@Override
public void create () {
super.create();
instructions = "Tap a triangle to ray cast\nLong press to toggle debug mode\nSwipe for next test\nCtrl+drag to rotate\nScroll to zoom";
shapeRenderer = new ShapeRenderer();
model = objLoader.loadModel(Gdx.files.internal("data/scene.obj"));
model.materials.get(0).clear();
model.materials.get(0).set(ColorAttribute.createDiffuse(Color.WHITE), ColorAttribute.createSpecular(Color.WHITE));
// Only indexed BvhTriangleMeshShape can be used for triangle picking.
btTriangleIndexVertexArray vertexArray = new btTriangleIndexVertexArray(model.meshParts);
triangleShape = new btBvhTriangleMeshShape(vertexArray, true);
triangleRaycastCallback = new MyTriangleRaycastCallback(Vector3.Zero, Vector3.Zero);
// Ignore intersection with mesh backfaces.
triangleRaycastCallback.setFlags(EFlags.kF_FilterBackfaces);
world.addConstructor("scene", new BulletConstructor(model, 0, triangleShape));
world.add("scene", 0, 0, 0);
disposables.add(model);
disposables.add(triangleRaycastCallback);
disposables.add(triangleShape);
disposables.add(vertexArray);
disposables.add(shapeRenderer);
}
@Override
public void render () {
super.render();
Gdx.gl.glLineWidth(5);
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
shapeRenderer.setColor(1, 0, 0, 1f);
shapeRenderer.line(selectedTriangleVertices[0], selectedTriangleVertices[1]);
shapeRenderer.line(selectedTriangleVertices[1], selectedTriangleVertices[2]);
shapeRenderer.line(selectedTriangleVertices[2], selectedTriangleVertices[0]);
shapeRenderer.end();
Gdx.gl.glLineWidth(1);
}
@Override
public boolean tap (float screenX, float screenY, int count, int button) {
Ray ray = camera.getPickRay(screenX, screenY);
rayFrom.set(ray.origin);
rayTo.set(ray.direction).scl(100).add(rayFrom);
// Clean the callback object for reuse.
triangleRaycastCallback.setHitFraction(1);
triangleRaycastCallback.clearReport();
triangleRaycastCallback.setFrom(rayFrom);
triangleRaycastCallback.setTo(rayTo);
// Ray casting is performed directly on the collision shape.
// The callback specifies the intersected MeshPart as well as triangle.
triangleShape.performRaycast(triangleRaycastCallback, rayFrom, rayTo);
int currentTriangleIndex = triangleRaycastCallback.triangleIndex;
int currentPartId = triangleRaycastCallback.partId;
if (currentTriangleIndex == -1 || currentPartId == -1) {
// No intersection was found.
return false;
}
// Get the position coordinates of the vertices belonging to intersected triangle.
Mesh mesh = model.meshParts.get(currentPartId).mesh;
FloatBuffer verticesBuffer = mesh.getVerticesBuffer();
ShortBuffer indicesBuffer = mesh.getIndicesBuffer();
int posOffset = mesh.getVertexAttributes().findByUsage(VertexAttributes.Usage.Position).offset / 4;
int vertexSize = mesh.getVertexSize() / 4;
int currentTriangleFirstVertexIndex = currentTriangleIndex * 3;
// Store the three vertices belonging to the selected triangle.
for (int i = 0; i < 3; i++) {
int currentVertexIndex = indicesBuffer.get(currentTriangleFirstVertexIndex + i);
int j = currentVertexIndex * vertexSize + posOffset;
float x = verticesBuffer.get(j++);
float y = verticesBuffer.get(j++);
float z = verticesBuffer.get(j);
selectedTriangleVertices[i].set(x, y, z);
}
return true;
}
}