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.collision.Collidable; 36 import com.jme3.collision.CollisionResult; 37 import com.jme3.collision.CollisionResults; 38 import com.jme3.export.*; 39 import com.jme3.math.Matrix4f; 40 import com.jme3.math.Ray; 41 import com.jme3.math.Triangle; 42 import com.jme3.math.Vector3f; 43 import com.jme3.util.TempVars; 44 import java.io.IOException; 45 import static java.lang.Math.max; 46 import static java.lang.Math.min; 47 import java.util.ArrayList; 48 49 /** 50 * Bounding Interval Hierarchy. 51 * Based on: 52 * 53 * Instant Ray Tracing: The Bounding Interval Hierarchy 54 * By Carsten Wchter and Alexander Keller 55 */ 56 public final class BIHNode implements Savable { 57 58 private int leftIndex, rightIndex; 59 private BIHNode left; 60 private BIHNode right; 61 private float leftPlane; 62 private float rightPlane; 63 private int axis; 64 65 //Do not do this: It increases memory usage of each BIHNode by at least 56 bytes! 66 // 67 //private Triangle tmpTriangle = new Triangle(); 68 69 public BIHNode(int l, int r) { 70 leftIndex = l; 71 rightIndex = r; 72 axis = 3; // indicates leaf 73 } 74 75 public BIHNode(int axis) { 76 this.axis = axis; 77 } 78 79 public BIHNode() { 80 } 81 82 public BIHNode getLeftChild() { 83 return left; 84 } 85 86 public void setLeftChild(BIHNode left) { 87 this.left = left; 88 } 89 90 public float getLeftPlane() { 91 return leftPlane; 92 } 93 94 public void setLeftPlane(float leftPlane) { 95 this.leftPlane = leftPlane; 96 } 97 98 public BIHNode getRightChild() { 99 return right; 100 } 101 102 public void setRightChild(BIHNode right) { 103 this.right = right; 104 } 105 106 public float getRightPlane() { 107 return rightPlane; 108 } 109 110 public void setRightPlane(float rightPlane) { 111 this.rightPlane = rightPlane; 112 } 113 114 public void write(JmeExporter ex) throws IOException { 115 OutputCapsule oc = ex.getCapsule(this); 116 oc.write(leftIndex, "left_index", 0); 117 oc.write(rightIndex, "right_index", 0); 118 oc.write(leftPlane, "left_plane", 0); 119 oc.write(rightPlane, "right_plane", 0); 120 oc.write(axis, "axis", 0); 121 oc.write(left, "left_node", null); 122 oc.write(right, "right_node", null); 123 } 124 125 public void read(JmeImporter im) throws IOException { 126 InputCapsule ic = im.getCapsule(this); 127 leftIndex = ic.readInt("left_index", 0); 128 rightIndex = ic.readInt("right_index", 0); 129 leftPlane = ic.readFloat("left_plane", 0); 130 rightPlane = ic.readFloat("right_plane", 0); 131 axis = ic.readInt("axis", 0); 132 left = (BIHNode) ic.readSavable("left_node", null); 133 right = (BIHNode) ic.readSavable("right_node", null); 134 } 135 136 public static final class BIHStackData { 137 138 private final BIHNode node; 139 private final float min, max; 140 141 BIHStackData(BIHNode node, float min, float max) { 142 this.node = node; 143 this.min = min; 144 this.max = max; 145 } 146 } 147 148 public final int intersectWhere(Collidable col, 149 BoundingBox box, 150 Matrix4f worldMatrix, 151 BIHTree tree, 152 CollisionResults results) { 153 154 TempVars vars = TempVars.get(); 155 ArrayList<BIHStackData> stack = vars.bihStack; 156 stack.clear(); 157 158 float[] minExts = {box.getCenter().x - box.getXExtent(), 159 box.getCenter().y - box.getYExtent(), 160 box.getCenter().z - box.getZExtent()}; 161 162 float[] maxExts = {box.getCenter().x + box.getXExtent(), 163 box.getCenter().y + box.getYExtent(), 164 box.getCenter().z + box.getZExtent()}; 165 166 stack.add(new BIHStackData(this, 0, 0)); 167 168 Triangle t = new Triangle(); 169 int cols = 0; 170 171 stackloop: 172 while (stack.size() > 0) { 173 BIHNode node = stack.remove(stack.size() - 1).node; 174 175 while (node.axis != 3) { 176 int a = node.axis; 177 178 float maxExt = maxExts[a]; 179 float minExt = minExts[a]; 180 181 if (node.leftPlane < node.rightPlane) { 182 // means there's a gap in the middle 183 // if the box is in that gap, we stop there 184 if (minExt > node.leftPlane 185 && maxExt < node.rightPlane) { 186 continue stackloop; 187 } 188 } 189 190 if (maxExt < node.rightPlane) { 191 node = node.left; 192 } else if (minExt > node.leftPlane) { 193 node = node.right; 194 } else { 195 stack.add(new BIHStackData(node.right, 0, 0)); 196 node = node.left; 197 } 198 // if (maxExt < node.leftPlane 199 // && maxExt < node.rightPlane){ 200 // node = node.left; 201 // }else if (minExt > node.leftPlane 202 // && minExt > node.rightPlane){ 203 // node = node.right; 204 // }else{ 205 206 // } 207 } 208 209 for (int i = node.leftIndex; i <= node.rightIndex; i++) { 210 tree.getTriangle(i, t.get1(), t.get2(), t.get3()); 211 if (worldMatrix != null) { 212 worldMatrix.mult(t.get1(), t.get1()); 213 worldMatrix.mult(t.get2(), t.get2()); 214 worldMatrix.mult(t.get3(), t.get3()); 215 } 216 217 int added = col.collideWith(t, results); 218 219 if (added > 0) { 220 int index = tree.getTriangleIndex(i); 221 int start = results.size() - added; 222 223 for (int j = start; j < results.size(); j++) { 224 CollisionResult cr = results.getCollisionDirect(j); 225 cr.setTriangleIndex(index); 226 } 227 228 cols += added; 229 } 230 } 231 } 232 vars.release(); 233 return cols; 234 } 235 236 public final int intersectBrute(Ray r, 237 Matrix4f worldMatrix, 238 BIHTree tree, 239 float sceneMin, 240 float sceneMax, 241 CollisionResults results) { 242 float tHit = Float.POSITIVE_INFINITY; 243 244 TempVars vars = TempVars.get(); 245 246 Vector3f v1 = vars.vect1, 247 v2 = vars.vect2, 248 v3 = vars.vect3; 249 250 int cols = 0; 251 252 ArrayList<BIHStackData> stack = vars.bihStack; 253 stack.clear(); 254 stack.add(new BIHStackData(this, 0, 0)); 255 stackloop: 256 while (stack.size() > 0) { 257 258 BIHStackData data = stack.remove(stack.size() - 1); 259 BIHNode node = data.node; 260 261 leafloop: 262 while (node.axis != 3) { // while node is not a leaf 263 BIHNode nearNode, farNode; 264 nearNode = node.left; 265 farNode = node.right; 266 267 stack.add(new BIHStackData(farNode, 0, 0)); 268 node = nearNode; 269 } 270 271 // a leaf 272 for (int i = node.leftIndex; i <= node.rightIndex; i++) { 273 tree.getTriangle(i, v1, v2, v3); 274 275 if (worldMatrix != null) { 276 worldMatrix.mult(v1, v1); 277 worldMatrix.mult(v2, v2); 278 worldMatrix.mult(v3, v3); 279 } 280 281 float t = r.intersects(v1, v2, v3); 282 if (t < tHit) { 283 tHit = t; 284 Vector3f contactPoint = new Vector3f(r.direction).multLocal(tHit).addLocal(r.origin); 285 CollisionResult cr = new CollisionResult(contactPoint, tHit); 286 cr.setTriangleIndex(tree.getTriangleIndex(i)); 287 results.addCollision(cr); 288 cols++; 289 } 290 } 291 } 292 vars.release(); 293 return cols; 294 } 295 296 public final int intersectWhere(Ray r, 297 Matrix4f worldMatrix, 298 BIHTree tree, 299 float sceneMin, 300 float sceneMax, 301 CollisionResults results) { 302 303 TempVars vars = TempVars.get(); 304 ArrayList<BIHStackData> stack = vars.bihStack; 305 stack.clear(); 306 307 // float tHit = Float.POSITIVE_INFINITY; 308 309 Vector3f o = vars.vect1.set(r.getOrigin()); 310 Vector3f d = vars.vect2.set(r.getDirection()); 311 312 Matrix4f inv =vars.tempMat4.set(worldMatrix).invertLocal(); 313 314 inv.mult(r.getOrigin(), r.getOrigin()); 315 316 // Fixes rotation collision bug 317 inv.multNormal(r.getDirection(), r.getDirection()); 318 // inv.multNormalAcross(r.getDirection(), r.getDirection()); 319 320 float[] origins = {r.getOrigin().x, 321 r.getOrigin().y, 322 r.getOrigin().z}; 323 324 float[] invDirections = {1f / r.getDirection().x, 325 1f / r.getDirection().y, 326 1f / r.getDirection().z}; 327 328 r.getDirection().normalizeLocal(); 329 330 Vector3f v1 = vars.vect3, 331 v2 = vars.vect4, 332 v3 = vars.vect5; 333 int cols = 0; 334 335 stack.add(new BIHStackData(this, sceneMin, sceneMax)); 336 stackloop: 337 while (stack.size() > 0) { 338 339 BIHStackData data = stack.remove(stack.size() - 1); 340 BIHNode node = data.node; 341 float tMin = data.min, 342 tMax = data.max; 343 344 if (tMax < tMin) { 345 continue; 346 } 347 348 leafloop: 349 while (node.axis != 3) { // while node is not a leaf 350 int a = node.axis; 351 352 // find the origin and direction value for the given axis 353 float origin = origins[a]; 354 float invDirection = invDirections[a]; 355 356 float tNearSplit, tFarSplit; 357 BIHNode nearNode, farNode; 358 359 tNearSplit = (node.leftPlane - origin) * invDirection; 360 tFarSplit = (node.rightPlane - origin) * invDirection; 361 nearNode = node.left; 362 farNode = node.right; 363 364 if (invDirection < 0) { 365 float tmpSplit = tNearSplit; 366 tNearSplit = tFarSplit; 367 tFarSplit = tmpSplit; 368 369 BIHNode tmpNode = nearNode; 370 nearNode = farNode; 371 farNode = tmpNode; 372 } 373 374 if (tMin > tNearSplit && tMax < tFarSplit) { 375 continue stackloop; 376 } 377 378 if (tMin > tNearSplit) { 379 tMin = max(tMin, tFarSplit); 380 node = farNode; 381 } else if (tMax < tFarSplit) { 382 tMax = min(tMax, tNearSplit); 383 node = nearNode; 384 } else { 385 stack.add(new BIHStackData(farNode, max(tMin, tFarSplit), tMax)); 386 tMax = min(tMax, tNearSplit); 387 node = nearNode; 388 } 389 } 390 391 // if ( (node.rightIndex - node.leftIndex) > minTrisPerNode){ 392 // // on demand subdivision 393 // node.subdivide(); 394 // stack.add(new BIHStackData(node, tMin, tMax)); 395 // continue stackloop; 396 // } 397 398 // a leaf 399 for (int i = node.leftIndex; i <= node.rightIndex; i++) { 400 tree.getTriangle(i, v1, v2, v3); 401 402 float t = r.intersects(v1, v2, v3); 403 if (!Float.isInfinite(t)) { 404 if (worldMatrix != null) { 405 worldMatrix.mult(v1, v1); 406 worldMatrix.mult(v2, v2); 407 worldMatrix.mult(v3, v3); 408 float t_world = new Ray(o, d).intersects(v1, v2, v3); 409 t = t_world; 410 } 411 412 Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null); 413 Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o); 414 float worldSpaceDist = o.distance(contactPoint); 415 416 CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist); 417 cr.setContactNormal(contactNormal); 418 cr.setTriangleIndex(tree.getTriangleIndex(i)); 419 results.addCollision(cr); 420 cols++; 421 } 422 } 423 } 424 vars.release(); 425 r.setOrigin(o); 426 r.setDirection(d); 427 428 return cols; 429 } 430 } 431