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