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 java.nio.FloatBuffer; 20 import java.nio.ShortBuffer; 21 22 import com.badlogic.gdx.Gdx; 23 import com.badlogic.gdx.graphics.Color; 24 import com.badlogic.gdx.graphics.Mesh; 25 import com.badlogic.gdx.graphics.VertexAttributes; 26 import com.badlogic.gdx.graphics.g3d.Model; 27 import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; 28 import com.badlogic.gdx.graphics.glutils.ShapeRenderer; 29 import com.badlogic.gdx.math.Vector3; 30 import com.badlogic.gdx.math.collision.Ray; 31 import com.badlogic.gdx.physics.bullet.collision.btBvhTriangleMeshShape; 32 import com.badlogic.gdx.physics.bullet.collision.btTriangleIndexVertexArray; 33 import com.badlogic.gdx.physics.bullet.collision.btTriangleRaycastCallback; 34 import com.badlogic.gdx.physics.bullet.collision.btTriangleRaycastCallback.EFlags; 35 import com.badlogic.gdx.physics.bullet.linearmath.btVector3; 36 37 /** @author jsjolund */ 38 public class TriangleRaycastTest extends BaseBulletTest { 39 40 private class MyTriangleRaycastCallback extends btTriangleRaycastCallback { 41 42 public Vector3 hitNormalLocal = new Vector3(); 43 public float hitFraction = 1; 44 public int partId = -1; 45 public int triangleIndex = -1; 46 47 private btVector3 tmpSetFrom = new btVector3(); 48 private btVector3 tmpSetTo = new btVector3(); 49 MyTriangleRaycastCallback(Vector3 from, Vector3 to)50 public MyTriangleRaycastCallback (Vector3 from, Vector3 to) { 51 super(from, to); 52 } 53 clearReport()54 public void clearReport () { 55 hitNormalLocal.setZero(); 56 hitFraction = 1; 57 partId = -1; 58 triangleIndex = -1; 59 } 60 61 @Override setHitFraction(float hitFraction)62 public void setHitFraction (float hitFraction) { 63 super.setHitFraction(hitFraction); 64 this.hitFraction = hitFraction; 65 } 66 67 @Override reportHit(Vector3 hitNormalLocal, float hitFraction, int partId, int triangleIndex)68 public float reportHit (Vector3 hitNormalLocal, float hitFraction, int partId, int triangleIndex) { 69 // The hit with lowest hitFraction is closest to the ray origin. 70 // We need to find the lowest hitFraction since the super class does not handle it for us. 71 if (hitFraction < this.hitFraction) { 72 this.hitNormalLocal.set(hitNormalLocal); 73 this.hitFraction = hitFraction; 74 this.partId = partId; 75 this.triangleIndex = triangleIndex; 76 } 77 return hitFraction; 78 } 79 setFrom(Vector3 value)80 public void setFrom (Vector3 value) { 81 tmpSetFrom.setValue(value.x, value.y, value.z); 82 super.setFrom(tmpSetFrom); 83 } 84 setTo(Vector3 value)85 public void setTo (Vector3 value) { 86 tmpSetTo.setValue(value.x, value.y, value.z); 87 super.setTo(tmpSetTo); 88 } 89 90 @Override dispose()91 public void dispose () { 92 tmpSetFrom.dispose(); 93 tmpSetTo.dispose(); 94 super.dispose(); 95 } 96 } 97 98 private Model model; 99 private btBvhTriangleMeshShape triangleShape; 100 private MyTriangleRaycastCallback triangleRaycastCallback; 101 private Vector3[] selectedTriangleVertices = {new Vector3(), new Vector3(), new Vector3()}; 102 private ShapeRenderer shapeRenderer; 103 private Vector3 rayFrom = new Vector3(); 104 private Vector3 rayTo = new Vector3(); 105 106 @Override create()107 public void create () { 108 super.create(); 109 instructions = "Tap a triangle to ray cast\nLong press to toggle debug mode\nSwipe for next test\nCtrl+drag to rotate\nScroll to zoom"; 110 111 shapeRenderer = new ShapeRenderer(); 112 113 model = objLoader.loadModel(Gdx.files.internal("data/scene.obj")); 114 model.materials.get(0).clear(); 115 model.materials.get(0).set(ColorAttribute.createDiffuse(Color.WHITE), ColorAttribute.createSpecular(Color.WHITE)); 116 117 // Only indexed BvhTriangleMeshShape can be used for triangle picking. 118 btTriangleIndexVertexArray vertexArray = new btTriangleIndexVertexArray(model.meshParts); 119 triangleShape = new btBvhTriangleMeshShape(vertexArray, true); 120 121 triangleRaycastCallback = new MyTriangleRaycastCallback(Vector3.Zero, Vector3.Zero); 122 // Ignore intersection with mesh backfaces. 123 triangleRaycastCallback.setFlags(EFlags.kF_FilterBackfaces); 124 125 world.addConstructor("scene", new BulletConstructor(model, 0, triangleShape)); 126 world.add("scene", 0, 0, 0); 127 128 disposables.add(model); 129 disposables.add(triangleRaycastCallback); 130 disposables.add(triangleShape); 131 disposables.add(vertexArray); 132 disposables.add(shapeRenderer); 133 134 } 135 136 @Override render()137 public void render () { 138 super.render(); 139 Gdx.gl.glLineWidth(5); 140 shapeRenderer.setProjectionMatrix(camera.combined); 141 shapeRenderer.begin(ShapeRenderer.ShapeType.Line); 142 shapeRenderer.setColor(1, 0, 0, 1f); 143 shapeRenderer.line(selectedTriangleVertices[0], selectedTriangleVertices[1]); 144 shapeRenderer.line(selectedTriangleVertices[1], selectedTriangleVertices[2]); 145 shapeRenderer.line(selectedTriangleVertices[2], selectedTriangleVertices[0]); 146 shapeRenderer.end(); 147 Gdx.gl.glLineWidth(1); 148 } 149 150 @Override tap(float screenX, float screenY, int count, int button)151 public boolean tap (float screenX, float screenY, int count, int button) { 152 Ray ray = camera.getPickRay(screenX, screenY); 153 rayFrom.set(ray.origin); 154 rayTo.set(ray.direction).scl(100).add(rayFrom); 155 156 // Clean the callback object for reuse. 157 triangleRaycastCallback.setHitFraction(1); 158 triangleRaycastCallback.clearReport(); 159 triangleRaycastCallback.setFrom(rayFrom); 160 triangleRaycastCallback.setTo(rayTo); 161 162 // Ray casting is performed directly on the collision shape. 163 // The callback specifies the intersected MeshPart as well as triangle. 164 triangleShape.performRaycast(triangleRaycastCallback, rayFrom, rayTo); 165 int currentTriangleIndex = triangleRaycastCallback.triangleIndex; 166 int currentPartId = triangleRaycastCallback.partId; 167 168 if (currentTriangleIndex == -1 || currentPartId == -1) { 169 // No intersection was found. 170 return false; 171 } 172 173 // Get the position coordinates of the vertices belonging to intersected triangle. 174 Mesh mesh = model.meshParts.get(currentPartId).mesh; 175 FloatBuffer verticesBuffer = mesh.getVerticesBuffer(); 176 ShortBuffer indicesBuffer = mesh.getIndicesBuffer(); 177 178 int posOffset = mesh.getVertexAttributes().findByUsage(VertexAttributes.Usage.Position).offset / 4; 179 int vertexSize = mesh.getVertexSize() / 4; 180 int currentTriangleFirstVertexIndex = currentTriangleIndex * 3; 181 182 // Store the three vertices belonging to the selected triangle. 183 for (int i = 0; i < 3; i++) { 184 int currentVertexIndex = indicesBuffer.get(currentTriangleFirstVertexIndex + i); 185 int j = currentVertexIndex * vertexSize + posOffset; 186 float x = verticesBuffer.get(j++); 187 float y = verticesBuffer.get(j++); 188 float z = verticesBuffer.get(j); 189 selectedTriangleVertices[i].set(x, y, z); 190 } 191 return true; 192 } 193 194 } 195