/* * Copyright (c) 2009-2010 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package jme3tools.optimize; import com.jme3.bounding.BoundingBox; import com.jme3.collision.CollisionResult; import com.jme3.collision.CollisionResults; import com.jme3.material.Material; import com.jme3.math.Matrix4f; import com.jme3.math.Ray; import com.jme3.math.Triangle; import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Geometry; import com.jme3.scene.debug.WireBox; import java.util.*; public class Octnode { static final Vector3f[] extentMult = new Vector3f[] { new Vector3f( 1, 1, 1), // right top forw new Vector3f(-1, 1, 1), // left top forw new Vector3f( 1,-1, 1), // right bot forw new Vector3f(-1,-1, 1), // left bot forw new Vector3f( 1, 1,-1), // right top back new Vector3f(-1, 1,-1), // left top back new Vector3f( 1,-1,-1), // right bot back new Vector3f(-1,-1,-1) // left bot back }; final BoundingBox bbox; final ArrayList tris; Geometry[] geoms; final Octnode[] children = new Octnode[8]; boolean leaf = false; FastOctnode fastNode; public Octnode(BoundingBox bbox, ArrayList tris){ this.bbox = bbox; this.tris = tris; } private BoundingBox getChildBound(int side){ float extent = bbox.getXExtent() * 0.5f; Vector3f center = new Vector3f(bbox.getCenter().x + extent * extentMult[side].x, bbox.getCenter().y + extent * extentMult[side].y, bbox.getCenter().z + extent * extentMult[side].z); return new BoundingBox(center, extent, extent, extent); } private float getAdditionCost(BoundingBox bbox, OCTTriangle t){ if (bbox.intersects(t.get1(), t.get2(), t.get3())){ float d1 = bbox.distanceToEdge(t.get1()); float d2 = bbox.distanceToEdge(t.get2()); float d3 = bbox.distanceToEdge(t.get3()); return d1 + d2 + d3; } return Float.POSITIVE_INFINITY; } private void expandBoxToContainTri(BoundingBox bbox, OCTTriangle t){ Vector3f min = bbox.getMin(null); Vector3f max = bbox.getMax(null); BoundingBox.checkMinMax(min, max, t.get1()); BoundingBox.checkMinMax(min, max, t.get2()); BoundingBox.checkMinMax(min, max, t.get3()); bbox.setMinMax(min, max); } private boolean contains(BoundingBox bbox, OCTTriangle t){ if (bbox.contains(t.get1()) && bbox.contains(t.get2()) && bbox.contains(t.get3())){ return true; } return false; } public void subdivide(int depth, int minTrisPerNode){ if (tris == null || depth > 50 || bbox.getVolume() < 0.01f || tris.size() < minTrisPerNode){ // no need to subdivide anymore leaf = true; return; } ArrayList keepTris = new ArrayList(); ArrayList[] trisForChild = new ArrayList[8]; BoundingBox[] boxForChild = new BoundingBox[8]; // create boxes for children for (int i = 0; i < 8; i++){ boxForChild[i] = getChildBound(i); trisForChild[i] = new ArrayList(); } for (OCTTriangle t : tris){ float lowestCost = Float.POSITIVE_INFINITY; int lowestIndex = -1; int numIntersecting = 0; for (int i = 0; i < 8; i++){ BoundingBox childBox = boxForChild[i]; float cost = getAdditionCost(childBox, t); if (cost < lowestCost){ lowestCost = cost; lowestIndex = i; numIntersecting++; } } if (numIntersecting < 8 && lowestIndex > -1){ trisForChild[lowestIndex].add(t); expandBoxToContainTri(boxForChild[lowestIndex], t); }else{ keepTris.add(t); } // boolean wasAdded = false; // for (int i = 0; i < 8; i++){ // BoundingBox childBox = boxForChild[i]; // if (contains(childBox, t)){ // trisForChild[i].add(t); // wasAdded = true; // break; // } // } // if (!wasAdded){ // keepTris.add(t); // } } tris.retainAll(keepTris); for (int i = 0; i < 8; i++){ if (trisForChild[i].size() > 0){ children[i] = new Octnode(boxForChild[i], trisForChild[i]); children[i].subdivide(depth + 1, minTrisPerNode); } } } public void subdivide(int minTrisPerNode){ subdivide(0, minTrisPerNode); } public void createFastOctnode(List globalGeomList){ fastNode = new FastOctnode(); if (geoms != null){ Collection geomsColl = Arrays.asList(geoms); List myOptimizedList = GeometryBatchFactory.makeBatches(geomsColl); int startIndex = globalGeomList.size(); globalGeomList.addAll(myOptimizedList); fastNode.setOffset(startIndex); fastNode.length = myOptimizedList.size(); }else{ fastNode.setOffset(0); fastNode.length = 0; } for (int i = 0; i < 8; i++){ if (children[i] != null){ children[i].createFastOctnode(globalGeomList); } } } public void generateFastOctnodeLinks(Octnode parent, Octnode nextSibling, int side){ fastNode.setSide(side); fastNode.next = nextSibling != null ? nextSibling.fastNode : null; // We set the next sibling property by going in reverse order Octnode prev = null; for (int i = 7; i >= 0; i--){ if (children[i] != null){ children[i].generateFastOctnodeLinks(this, prev, i); prev = children[i]; } } fastNode.child = prev != null ? prev.fastNode : null; } private void generateRenderSetNoCheck(Set renderSet, Camera cam){ if (geoms != null){ renderSet.addAll(Arrays.asList(geoms)); } for (int i = 0; i < 8; i++){ if (children[i] != null){ children[i].generateRenderSetNoCheck(renderSet, cam); } } } public void generateRenderSet(Set renderSet, Camera cam){ // generateRenderSetNoCheck(renderSet, cam); bbox.setCheckPlane(0); cam.setPlaneState(0); Camera.FrustumIntersect result = cam.contains(bbox); if (result != Camera.FrustumIntersect.Outside){ if (geoms != null){ renderSet.addAll(Arrays.asList(geoms)); } for (int i = 0; i < 8; i++){ if (children[i] != null){ if (result == Camera.FrustumIntersect.Inside){ children[i].generateRenderSetNoCheck(renderSet, cam); }else{ children[i].generateRenderSet(renderSet, cam); } } } } } public void collectTriangles(Geometry[] inGeoms){ if (tris.size() > 0){ List geomsList = TriangleCollector.gatherTris(inGeoms, tris); geoms = new Geometry[geomsList.size()]; geomsList.toArray(geoms); }else{ geoms = null; } for (int i = 0; i < 8; i++){ if (children[i] != null){ children[i].collectTriangles(inGeoms); } } } public void renderBounds(RenderQueue rq, Matrix4f transform, WireBox box, Material mat){ int numChilds = 0; for (int i = 0; i < 8; i++){ if (children[i] != null){ numChilds ++; break; } } if (geoms != null && numChilds == 0){ BoundingBox bbox2 = new BoundingBox(bbox); bbox.transform(transform, bbox2); // WireBox box = new WireBox(bbox2.getXExtent(), bbox2.getYExtent(), // bbox2.getZExtent()); // WireBox box = new WireBox(1,1,1); Geometry geom = new Geometry("bound", box); geom.setLocalTranslation(bbox2.getCenter()); geom.setLocalScale(bbox2.getXExtent(), bbox2.getYExtent(), bbox2.getZExtent()); geom.updateGeometricState(); geom.setMaterial(mat); rq.addToQueue(geom, Bucket.Opaque); box = null; geom = null; } for (int i = 0; i < 8; i++){ if (children[i] != null){ children[i].renderBounds(rq, transform, box, mat); } } } public final void intersectWhere(Ray r, Geometry[] geoms, float sceneMin, float sceneMax, CollisionResults results){ for (OCTTriangle t : tris){ float d = r.intersects(t.get1(), t.get2(), t.get3()); if (Float.isInfinite(d)) continue; Vector3f contactPoint = new Vector3f(r.getDirection()).multLocal(d).addLocal(r.getOrigin()); CollisionResult result = new CollisionResult(geoms[t.getGeometryIndex()], contactPoint, d, t.getTriangleIndex()); results.addCollision(result); } for (int i = 0; i < 8; i++){ Octnode child = children[i]; if (child == null) continue; if (child.bbox.intersects(r)){ child.intersectWhere(r, geoms, sceneMin, sceneMax, results); } } } }