Home | History | Annotate | Download | only in optimize
      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 
     33 package jme3tools.optimize;
     34 
     35 import com.jme3.bounding.BoundingBox;
     36 import com.jme3.collision.CollisionResult;
     37 import com.jme3.collision.CollisionResults;
     38 import com.jme3.material.Material;
     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.renderer.Camera;
     44 import com.jme3.renderer.queue.RenderQueue;
     45 import com.jme3.renderer.queue.RenderQueue.Bucket;
     46 import com.jme3.scene.Geometry;
     47 import com.jme3.scene.debug.WireBox;
     48 import java.util.*;
     49 
     50 public class Octnode {
     51 
     52     static final Vector3f[] extentMult = new Vector3f[]
     53     {
     54         new Vector3f( 1, 1, 1), // right top forw
     55         new Vector3f(-1, 1, 1), // left top forw
     56         new Vector3f( 1,-1, 1), // right bot forw
     57         new Vector3f(-1,-1, 1), // left bot forw
     58         new Vector3f( 1, 1,-1), // right top back
     59         new Vector3f(-1, 1,-1), // left top back
     60         new Vector3f( 1,-1,-1), // right bot back
     61         new Vector3f(-1,-1,-1)  // left bot back
     62     };
     63 
     64     final BoundingBox bbox;
     65     final ArrayList<OCTTriangle> tris;
     66     Geometry[] geoms;
     67     final Octnode[] children = new Octnode[8];
     68     boolean leaf = false;
     69     FastOctnode fastNode;
     70 
     71     public Octnode(BoundingBox bbox, ArrayList<OCTTriangle> tris){
     72         this.bbox = bbox;
     73         this.tris = tris;
     74     }
     75 
     76     private BoundingBox getChildBound(int side){
     77         float extent = bbox.getXExtent() * 0.5f;
     78         Vector3f center = new Vector3f(bbox.getCenter().x + extent * extentMult[side].x,
     79                                        bbox.getCenter().y + extent * extentMult[side].y,
     80                                        bbox.getCenter().z + extent * extentMult[side].z);
     81         return new BoundingBox(center, extent, extent, extent);
     82     }
     83 
     84     private float getAdditionCost(BoundingBox bbox, OCTTriangle t){
     85         if (bbox.intersects(t.get1(), t.get2(), t.get3())){
     86             float d1 = bbox.distanceToEdge(t.get1());
     87             float d2 = bbox.distanceToEdge(t.get2());
     88             float d3 = bbox.distanceToEdge(t.get3());
     89             return d1 + d2 + d3;
     90         }
     91         return Float.POSITIVE_INFINITY;
     92     }
     93 
     94     private void expandBoxToContainTri(BoundingBox bbox, OCTTriangle t){
     95         Vector3f min = bbox.getMin(null);
     96         Vector3f max = bbox.getMax(null);
     97         BoundingBox.checkMinMax(min, max, t.get1());
     98         BoundingBox.checkMinMax(min, max, t.get2());
     99         BoundingBox.checkMinMax(min, max, t.get3());
    100         bbox.setMinMax(min, max);
    101     }
    102 
    103     private boolean contains(BoundingBox bbox, OCTTriangle t){
    104         if (bbox.contains(t.get1()) &&
    105             bbox.contains(t.get2()) &&
    106             bbox.contains(t.get3())){
    107             return true;
    108         }
    109         return false;
    110     }
    111 
    112     public void subdivide(int depth, int minTrisPerNode){
    113         if (tris == null || depth > 50 || bbox.getVolume() < 0.01f || tris.size() < minTrisPerNode){
    114             // no need to subdivide anymore
    115             leaf = true;
    116             return;
    117         }
    118 
    119         ArrayList<OCTTriangle> keepTris = new ArrayList<OCTTriangle>();
    120         ArrayList[] trisForChild = new ArrayList[8];
    121         BoundingBox[] boxForChild = new BoundingBox[8];
    122         // create boxes for children
    123         for (int i = 0; i < 8; i++){
    124             boxForChild[i] = getChildBound(i);
    125             trisForChild[i] = new ArrayList<Triangle>();
    126         }
    127 
    128         for (OCTTriangle t : tris){
    129             float lowestCost = Float.POSITIVE_INFINITY;
    130             int lowestIndex = -1;
    131             int numIntersecting = 0;
    132             for (int i = 0; i < 8; i++){
    133                 BoundingBox childBox = boxForChild[i];
    134                 float cost = getAdditionCost(childBox, t);
    135                 if (cost < lowestCost){
    136                     lowestCost = cost;
    137                     lowestIndex = i;
    138                     numIntersecting++;
    139                 }
    140             }
    141             if (numIntersecting < 8 && lowestIndex > -1){
    142                 trisForChild[lowestIndex].add(t);
    143                 expandBoxToContainTri(boxForChild[lowestIndex], t);
    144             }else{
    145                 keepTris.add(t);
    146             }
    147 //            boolean wasAdded = false;
    148 //            for (int i = 0; i < 8; i++){
    149 //                BoundingBox childBox = boxForChild[i];
    150 //                if (contains(childBox, t)){
    151 //                    trisForChild[i].add(t);
    152 //                    wasAdded = true;
    153 //                    break;
    154 //                }
    155 //            }
    156 //            if (!wasAdded){
    157 //                keepTris.add(t);
    158 //            }
    159         }
    160         tris.retainAll(keepTris);
    161         for (int i = 0; i < 8; i++){
    162             if (trisForChild[i].size() > 0){
    163                 children[i] = new Octnode(boxForChild[i], trisForChild[i]);
    164                 children[i].subdivide(depth + 1, minTrisPerNode);
    165             }
    166         }
    167     }
    168 
    169     public void subdivide(int minTrisPerNode){
    170         subdivide(0, minTrisPerNode);
    171     }
    172 
    173     public void createFastOctnode(List<Geometry> globalGeomList){
    174         fastNode = new FastOctnode();
    175 
    176         if (geoms != null){
    177             Collection<Geometry> geomsColl = Arrays.asList(geoms);
    178             List<Geometry> myOptimizedList = GeometryBatchFactory.makeBatches(geomsColl);
    179 
    180             int startIndex = globalGeomList.size();
    181             globalGeomList.addAll(myOptimizedList);
    182 
    183             fastNode.setOffset(startIndex);
    184             fastNode.length = myOptimizedList.size();
    185         }else{
    186             fastNode.setOffset(0);
    187             fastNode.length = 0;
    188         }
    189 
    190         for (int i = 0; i < 8; i++){
    191             if (children[i] != null){
    192                 children[i].createFastOctnode(globalGeomList);
    193             }
    194         }
    195     }
    196 
    197     public void generateFastOctnodeLinks(Octnode parent, Octnode nextSibling, int side){
    198         fastNode.setSide(side);
    199         fastNode.next = nextSibling != null ? nextSibling.fastNode : null;
    200 
    201         // We set the next sibling property by going in reverse order
    202         Octnode prev = null;
    203         for (int i = 7; i >= 0; i--){
    204             if (children[i] != null){
    205                 children[i].generateFastOctnodeLinks(this, prev, i);
    206                 prev = children[i];
    207             }
    208         }
    209         fastNode.child = prev != null ? prev.fastNode : null;
    210     }
    211 
    212     private void generateRenderSetNoCheck(Set<Geometry> renderSet, Camera cam){
    213         if (geoms != null){
    214             renderSet.addAll(Arrays.asList(geoms));
    215         }
    216         for (int i = 0; i < 8; i++){
    217             if (children[i] != null){
    218                 children[i].generateRenderSetNoCheck(renderSet, cam);
    219             }
    220         }
    221     }
    222 
    223     public void generateRenderSet(Set<Geometry> renderSet, Camera cam){
    224 //        generateRenderSetNoCheck(renderSet, cam);
    225 
    226         bbox.setCheckPlane(0);
    227         cam.setPlaneState(0);
    228         Camera.FrustumIntersect result = cam.contains(bbox);
    229         if (result != Camera.FrustumIntersect.Outside){
    230             if (geoms != null){
    231                 renderSet.addAll(Arrays.asList(geoms));
    232             }
    233             for (int i = 0; i < 8; i++){
    234                 if (children[i] != null){
    235                     if (result == Camera.FrustumIntersect.Inside){
    236                         children[i].generateRenderSetNoCheck(renderSet, cam);
    237                     }else{
    238                         children[i].generateRenderSet(renderSet, cam);
    239                     }
    240                 }
    241             }
    242         }
    243     }
    244 
    245     public void collectTriangles(Geometry[] inGeoms){
    246         if (tris.size() > 0){
    247             List<Geometry> geomsList = TriangleCollector.gatherTris(inGeoms, tris);
    248             geoms = new Geometry[geomsList.size()];
    249             geomsList.toArray(geoms);
    250         }else{
    251             geoms = null;
    252         }
    253         for (int i = 0; i < 8; i++){
    254             if (children[i] != null){
    255                 children[i].collectTriangles(inGeoms);
    256             }
    257         }
    258     }
    259 
    260     public void renderBounds(RenderQueue rq, Matrix4f transform, WireBox box, Material mat){
    261         int numChilds = 0;
    262         for (int i = 0; i < 8; i++){
    263             if (children[i] != null){
    264                 numChilds ++;
    265                 break;
    266             }
    267         }
    268         if (geoms != null && numChilds == 0){
    269             BoundingBox bbox2 = new BoundingBox(bbox);
    270             bbox.transform(transform, bbox2);
    271 //            WireBox box = new WireBox(bbox2.getXExtent(), bbox2.getYExtent(),
    272 //                                      bbox2.getZExtent());
    273 //            WireBox box = new WireBox(1,1,1);
    274 
    275             Geometry geom = new Geometry("bound", box);
    276             geom.setLocalTranslation(bbox2.getCenter());
    277             geom.setLocalScale(bbox2.getXExtent(), bbox2.getYExtent(),
    278                                bbox2.getZExtent());
    279             geom.updateGeometricState();
    280             geom.setMaterial(mat);
    281             rq.addToQueue(geom, Bucket.Opaque);
    282             box = null;
    283             geom = null;
    284         }
    285         for (int i = 0; i < 8; i++){
    286             if (children[i] != null){
    287                 children[i].renderBounds(rq, transform, box, mat);
    288             }
    289         }
    290     }
    291 
    292     public final void intersectWhere(Ray r, Geometry[] geoms, float sceneMin, float sceneMax,
    293                                             CollisionResults results){
    294         for (OCTTriangle t : tris){
    295             float d = r.intersects(t.get1(), t.get2(), t.get3());
    296             if (Float.isInfinite(d))
    297                 continue;
    298 
    299             Vector3f contactPoint = new Vector3f(r.getDirection()).multLocal(d).addLocal(r.getOrigin());
    300             CollisionResult result = new CollisionResult(geoms[t.getGeometryIndex()],
    301                                                          contactPoint,
    302                                                          d,
    303                                                          t.getTriangleIndex());
    304             results.addCollision(result);
    305         }
    306         for (int i = 0; i < 8; i++){
    307             Octnode child = children[i];
    308             if (child == null)
    309                 continue;
    310 
    311             if (child.bbox.intersects(r)){
    312                 child.intersectWhere(r, geoms, sceneMin, sceneMax, results);
    313             }
    314         }
    315     }
    316 
    317 }
    318