1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.jme3.collision.bih; 33 34 import com.jme3.bounding.BoundingBox; 35 import com.jme3.bounding.BoundingSphere; 36 import com.jme3.bounding.BoundingVolume; 37 import com.jme3.collision.Collidable; 38 import com.jme3.collision.CollisionResults; 39 import com.jme3.collision.UnsupportedCollisionException; 40 import com.jme3.export.InputCapsule; 41 import com.jme3.export.JmeExporter; 42 import com.jme3.export.JmeImporter; 43 import com.jme3.export.OutputCapsule; 44 import com.jme3.math.FastMath; 45 import com.jme3.math.Matrix4f; 46 import com.jme3.math.Ray; 47 import com.jme3.math.Vector3f; 48 import com.jme3.scene.CollisionData; 49 import com.jme3.scene.Mesh; 50 import com.jme3.scene.Mesh.Mode; 51 import com.jme3.scene.VertexBuffer.Type; 52 import com.jme3.scene.mesh.IndexBuffer; 53 import com.jme3.scene.mesh.VirtualIndexBuffer; 54 import com.jme3.scene.mesh.WrappedIndexBuffer; 55 import com.jme3.util.TempVars; 56 import java.io.IOException; 57 import static java.lang.Math.max; 58 import java.nio.FloatBuffer; 59 60 public class BIHTree implements CollisionData { 61 62 public static final int MAX_TREE_DEPTH = 100; 63 public static final int MAX_TRIS_PER_NODE = 21; 64 private Mesh mesh; 65 private BIHNode root; 66 private int maxTrisPerNode; 67 private int numTris; 68 private float[] pointData; 69 private int[] triIndices; 70 71 private transient CollisionResults boundResults = new CollisionResults(); 72 private transient float[] bihSwapTmp; 73 74 private static final TriangleAxisComparator[] comparators = new TriangleAxisComparator[] 75 { 76 new TriangleAxisComparator(0), 77 new TriangleAxisComparator(1), 78 new TriangleAxisComparator(2) 79 }; 80 initTriList(FloatBuffer vb, IndexBuffer ib)81 private void initTriList(FloatBuffer vb, IndexBuffer ib) { 82 pointData = new float[numTris * 3 * 3]; 83 int p = 0; 84 for (int i = 0; i < numTris * 3; i += 3) { 85 int vert = ib.get(i) * 3; 86 pointData[p++] = vb.get(vert++); 87 pointData[p++] = vb.get(vert++); 88 pointData[p++] = vb.get(vert); 89 90 vert = ib.get(i + 1) * 3; 91 pointData[p++] = vb.get(vert++); 92 pointData[p++] = vb.get(vert++); 93 pointData[p++] = vb.get(vert); 94 95 vert = ib.get(i + 2) * 3; 96 pointData[p++] = vb.get(vert++); 97 pointData[p++] = vb.get(vert++); 98 pointData[p++] = vb.get(vert); 99 } 100 101 triIndices = new int[numTris]; 102 for (int i = 0; i < numTris; i++) { 103 triIndices[i] = i; 104 } 105 } 106 BIHTree(Mesh mesh, int maxTrisPerNode)107 public BIHTree(Mesh mesh, int maxTrisPerNode) { 108 this.mesh = mesh; 109 this.maxTrisPerNode = maxTrisPerNode; 110 111 if (maxTrisPerNode < 1 || mesh == null) { 112 throw new IllegalArgumentException(); 113 } 114 115 bihSwapTmp = new float[9]; 116 117 FloatBuffer vb = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); 118 IndexBuffer ib = mesh.getIndexBuffer(); 119 if (ib == null) { 120 ib = new VirtualIndexBuffer(mesh.getVertexCount(), mesh.getMode()); 121 } else if (mesh.getMode() != Mode.Triangles) { 122 ib = new WrappedIndexBuffer(mesh); 123 } 124 125 numTris = ib.size() / 3; 126 initTriList(vb, ib); 127 } 128 BIHTree(Mesh mesh)129 public BIHTree(Mesh mesh) { 130 this(mesh, MAX_TRIS_PER_NODE); 131 } 132 BIHTree()133 public BIHTree() { 134 } 135 construct()136 public void construct() { 137 BoundingBox sceneBbox = createBox(0, numTris - 1); 138 root = createNode(0, numTris - 1, sceneBbox, 0); 139 } 140 createBox(int l, int r)141 private BoundingBox createBox(int l, int r) { 142 TempVars vars = TempVars.get(); 143 144 Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)); 145 Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)); 146 147 Vector3f v1 = vars.vect3, 148 v2 = vars.vect4, 149 v3 = vars.vect5; 150 151 for (int i = l; i <= r; i++) { 152 getTriangle(i, v1, v2, v3); 153 BoundingBox.checkMinMax(min, max, v1); 154 BoundingBox.checkMinMax(min, max, v2); 155 BoundingBox.checkMinMax(min, max, v3); 156 } 157 158 BoundingBox bbox = new BoundingBox(min, max); 159 vars.release(); 160 return bbox; 161 } 162 getTriangleIndex(int triIndex)163 int getTriangleIndex(int triIndex) { 164 return triIndices[triIndex]; 165 } 166 sortTriangles(int l, int r, float split, int axis)167 private int sortTriangles(int l, int r, float split, int axis) { 168 int pivot = l; 169 int j = r; 170 171 TempVars vars = TempVars.get(); 172 173 Vector3f v1 = vars.vect1, 174 v2 = vars.vect2, 175 v3 = vars.vect3; 176 177 while (pivot <= j) { 178 getTriangle(pivot, v1, v2, v3); 179 v1.addLocal(v2).addLocal(v3).multLocal(FastMath.ONE_THIRD); 180 if (v1.get(axis) > split) { 181 swapTriangles(pivot, j); 182 --j; 183 } else { 184 ++pivot; 185 } 186 } 187 188 vars.release(); 189 pivot = (pivot == l && j < pivot) ? j : pivot; 190 return pivot; 191 } 192 setMinMax(BoundingBox bbox, boolean doMin, int axis, float value)193 private void setMinMax(BoundingBox bbox, boolean doMin, int axis, float value) { 194 Vector3f min = bbox.getMin(null); 195 Vector3f max = bbox.getMax(null); 196 197 if (doMin) { 198 min.set(axis, value); 199 } else { 200 max.set(axis, value); 201 } 202 203 bbox.setMinMax(min, max); 204 } 205 getMinMax(BoundingBox bbox, boolean doMin, int axis)206 private float getMinMax(BoundingBox bbox, boolean doMin, int axis) { 207 if (doMin) { 208 return bbox.getMin(null).get(axis); 209 } else { 210 return bbox.getMax(null).get(axis); 211 } 212 } 213 214 // private BIHNode createNode2(int l, int r, BoundingBox nodeBbox, int depth){ 215 // if ((r - l) < maxTrisPerNode || depth > 100) 216 // return createLeaf(l, r); 217 // 218 // BoundingBox currentBox = createBox(l, r); 219 // int axis = depth % 3; 220 // float split = currentBox.getCenter().get(axis); 221 // 222 // TriangleAxisComparator comparator = comparators[axis]; 223 // Arrays.sort(tris, l, r, comparator); 224 // int splitIndex = -1; 225 // 226 // float leftPlane, rightPlane = Float.POSITIVE_INFINITY; 227 // leftPlane = tris[l].getExtreme(axis, false); 228 // for (int i = l; i <= r; i++){ 229 // BIHTriangle tri = tris[i]; 230 // if (splitIndex == -1){ 231 // float v = tri.getCenter().get(axis); 232 // if (v > split){ 233 // if (i == 0){ 234 // // no left plane 235 // splitIndex = -2; 236 // }else{ 237 // splitIndex = i; 238 // // first triangle assigned to right 239 // rightPlane = tri.getExtreme(axis, true); 240 // } 241 // }else{ 242 // // triangle assigned to left 243 // float ex = tri.getExtreme(axis, false); 244 // if (ex > leftPlane) 245 // leftPlane = ex; 246 // } 247 // }else{ 248 // float ex = tri.getExtreme(axis, true); 249 // if (ex < rightPlane) 250 // rightPlane = ex; 251 // } 252 // } 253 // 254 // if (splitIndex < 0){ 255 // splitIndex = (r - l) / 2; 256 // 257 // leftPlane = Float.NEGATIVE_INFINITY; 258 // rightPlane = Float.POSITIVE_INFINITY; 259 // 260 // for (int i = l; i < splitIndex; i++){ 261 // float ex = tris[i].getExtreme(axis, false); 262 // if (ex > leftPlane){ 263 // leftPlane = ex; 264 // } 265 // } 266 // for (int i = splitIndex; i <= r; i++){ 267 // float ex = tris[i].getExtreme(axis, true); 268 // if (ex < rightPlane){ 269 // rightPlane = ex; 270 // } 271 // } 272 // } 273 // 274 // BIHNode node = new BIHNode(axis); 275 // node.leftPlane = leftPlane; 276 // node.rightPlane = rightPlane; 277 // 278 // node.leftIndex = l; 279 // node.rightIndex = r; 280 // 281 // BoundingBox leftBbox = new BoundingBox(currentBox); 282 // setMinMax(leftBbox, false, axis, split); 283 // node.left = createNode2(l, splitIndex-1, leftBbox, depth+1); 284 // 285 // BoundingBox rightBbox = new BoundingBox(currentBox); 286 // setMinMax(rightBbox, true, axis, split); 287 // node.right = createNode2(splitIndex, r, rightBbox, depth+1); 288 // 289 // return node; 290 // } createNode(int l, int r, BoundingBox nodeBbox, int depth)291 private BIHNode createNode(int l, int r, BoundingBox nodeBbox, int depth) { 292 if ((r - l) < maxTrisPerNode || depth > MAX_TREE_DEPTH) { 293 return new BIHNode(l, r); 294 } 295 296 BoundingBox currentBox = createBox(l, r); 297 298 Vector3f exteriorExt = nodeBbox.getExtent(null); 299 Vector3f interiorExt = currentBox.getExtent(null); 300 exteriorExt.subtractLocal(interiorExt); 301 302 int axis = 0; 303 if (exteriorExt.x > exteriorExt.y) { 304 if (exteriorExt.x > exteriorExt.z) { 305 axis = 0; 306 } else { 307 axis = 2; 308 } 309 } else { 310 if (exteriorExt.y > exteriorExt.z) { 311 axis = 1; 312 } else { 313 axis = 2; 314 } 315 } 316 if (exteriorExt.equals(Vector3f.ZERO)) { 317 axis = 0; 318 } 319 320 // Arrays.sort(tris, l, r, comparators[axis]); 321 float split = currentBox.getCenter().get(axis); 322 int pivot = sortTriangles(l, r, split, axis); 323 if (pivot == l || pivot == r) { 324 pivot = (r + l) / 2; 325 } 326 327 //If one of the partitions is empty, continue with recursion: same level but different bbox 328 if (pivot < l) { 329 //Only right 330 BoundingBox rbbox = new BoundingBox(currentBox); 331 setMinMax(rbbox, true, axis, split); 332 return createNode(l, r, rbbox, depth + 1); 333 } else if (pivot > r) { 334 //Only left 335 BoundingBox lbbox = new BoundingBox(currentBox); 336 setMinMax(lbbox, false, axis, split); 337 return createNode(l, r, lbbox, depth + 1); 338 } else { 339 //Build the node 340 BIHNode node = new BIHNode(axis); 341 342 //Left child 343 BoundingBox lbbox = new BoundingBox(currentBox); 344 setMinMax(lbbox, false, axis, split); 345 346 //The left node right border is the plane most right 347 node.setLeftPlane(getMinMax(createBox(l, max(l, pivot - 1)), false, axis)); 348 node.setLeftChild(createNode(l, max(l, pivot - 1), lbbox, depth + 1)); //Recursive call 349 350 //Right Child 351 BoundingBox rbbox = new BoundingBox(currentBox); 352 setMinMax(rbbox, true, axis, split); 353 //The right node left border is the plane most left 354 node.setRightPlane(getMinMax(createBox(pivot, r), true, axis)); 355 node.setRightChild(createNode(pivot, r, rbbox, depth + 1)); //Recursive call 356 357 return node; 358 } 359 } 360 getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3)361 public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3) { 362 int pointIndex = index * 9; 363 364 v1.x = pointData[pointIndex++]; 365 v1.y = pointData[pointIndex++]; 366 v1.z = pointData[pointIndex++]; 367 368 v2.x = pointData[pointIndex++]; 369 v2.y = pointData[pointIndex++]; 370 v2.z = pointData[pointIndex++]; 371 372 v3.x = pointData[pointIndex++]; 373 v3.y = pointData[pointIndex++]; 374 v3.z = pointData[pointIndex++]; 375 } 376 swapTriangles(int index1, int index2)377 public void swapTriangles(int index1, int index2) { 378 int p1 = index1 * 9; 379 int p2 = index2 * 9; 380 381 // store p1 in tmp 382 System.arraycopy(pointData, p1, bihSwapTmp, 0, 9); 383 384 // copy p2 to p1 385 System.arraycopy(pointData, p2, pointData, p1, 9); 386 387 // copy tmp to p2 388 System.arraycopy(bihSwapTmp, 0, pointData, p2, 9); 389 390 // swap indices 391 int tmp2 = triIndices[index1]; 392 triIndices[index1] = triIndices[index2]; 393 triIndices[index2] = tmp2; 394 } 395 collideWithRay(Ray r, Matrix4f worldMatrix, BoundingVolume worldBound, CollisionResults results)396 private int collideWithRay(Ray r, 397 Matrix4f worldMatrix, 398 BoundingVolume worldBound, 399 CollisionResults results) { 400 401 boundResults.clear(); 402 worldBound.collideWith(r, boundResults); 403 if (boundResults.size() > 0) { 404 float tMin = boundResults.getClosestCollision().getDistance(); 405 float tMax = boundResults.getFarthestCollision().getDistance(); 406 407 if (tMax <= 0) { 408 tMax = Float.POSITIVE_INFINITY; 409 } else if (tMin == tMax) { 410 tMin = 0; 411 } 412 413 if (tMin <= 0) { 414 tMin = 0; 415 } 416 417 if (r.getLimit() < Float.POSITIVE_INFINITY) { 418 tMax = Math.min(tMax, r.getLimit()); 419 if (tMin > tMax){ 420 return 0; 421 } 422 } 423 424 // return root.intersectBrute(r, worldMatrix, this, tMin, tMax, results); 425 return root.intersectWhere(r, worldMatrix, this, tMin, tMax, results); 426 } 427 return 0; 428 } 429 collideWithBoundingVolume(BoundingVolume bv, Matrix4f worldMatrix, CollisionResults results)430 private int collideWithBoundingVolume(BoundingVolume bv, 431 Matrix4f worldMatrix, 432 CollisionResults results) { 433 BoundingBox bbox; 434 if (bv instanceof BoundingSphere) { 435 BoundingSphere sphere = (BoundingSphere) bv; 436 bbox = new BoundingBox(bv.getCenter().clone(), sphere.getRadius(), 437 sphere.getRadius(), 438 sphere.getRadius()); 439 } else if (bv instanceof BoundingBox) { 440 bbox = new BoundingBox((BoundingBox) bv); 441 } else { 442 throw new UnsupportedCollisionException(); 443 } 444 445 bbox.transform(worldMatrix.invert(), bbox); 446 return root.intersectWhere(bv, bbox, worldMatrix, this, results); 447 } 448 collideWith(Collidable other, Matrix4f worldMatrix, BoundingVolume worldBound, CollisionResults results)449 public int collideWith(Collidable other, 450 Matrix4f worldMatrix, 451 BoundingVolume worldBound, 452 CollisionResults results) { 453 454 if (other instanceof Ray) { 455 Ray ray = (Ray) other; 456 return collideWithRay(ray, worldMatrix, worldBound, results); 457 } else if (other instanceof BoundingVolume) { 458 BoundingVolume bv = (BoundingVolume) other; 459 return collideWithBoundingVolume(bv, worldMatrix, results); 460 } else { 461 throw new UnsupportedCollisionException(); 462 } 463 } 464 write(JmeExporter ex)465 public void write(JmeExporter ex) throws IOException { 466 OutputCapsule oc = ex.getCapsule(this); 467 oc.write(mesh, "mesh", null); 468 oc.write(root, "root", null); 469 oc.write(maxTrisPerNode, "tris_per_node", 0); 470 oc.write(pointData, "points", null); 471 oc.write(triIndices, "indices", null); 472 } 473 read(JmeImporter im)474 public void read(JmeImporter im) throws IOException { 475 InputCapsule ic = im.getCapsule(this); 476 mesh = (Mesh) ic.readSavable("mesh", null); 477 root = (BIHNode) ic.readSavable("root", null); 478 maxTrisPerNode = ic.readInt("tris_per_node", 0); 479 pointData = ic.readFloatArray("points", null); 480 triIndices = ic.readIntArray("indices", null); 481 } 482 } 483