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 com.jme3.scene.control; 34 35 import com.jme3.bounding.BoundingVolume; 36 import com.jme3.export.InputCapsule; 37 import com.jme3.export.JmeExporter; 38 import com.jme3.export.JmeImporter; 39 import com.jme3.export.OutputCapsule; 40 import com.jme3.math.FastMath; 41 import com.jme3.renderer.Camera; 42 import com.jme3.renderer.RenderManager; 43 import com.jme3.renderer.ViewPort; 44 import com.jme3.scene.Geometry; 45 import com.jme3.scene.Mesh; 46 import com.jme3.scene.Spatial; 47 import java.io.IOException; 48 49 /** 50 * Determines what Level of Detail a spatial should be, based on how many pixels 51 * on the screen the spatial is taking up. The more pixels covered, the more detailed 52 * the spatial should be. 53 * It calculates the area of the screen that the spatial covers by using its bounding box. 54 * When initializing, it will ask the spatial for how many triangles it has for each LOD. 55 * It then uses that, along with the trisPerPixel value to determine what LOD it should be at. 56 * It requires the camera to do this. 57 * The controlRender method is called each frame and will update the spatial's LOD 58 * if the camera has moved by a specified amount. 59 */ 60 public class LodControl extends AbstractControl implements Cloneable { 61 62 private float trisPerPixel = 1f; 63 private float distTolerance = 1f; 64 private float lastDistance = 0f; 65 private int lastLevel = 0; 66 private int numLevels; 67 private int[] numTris; 68 69 /** 70 * Creates a new <code>LodControl</code>. 71 */ 72 public LodControl(){ 73 } 74 75 /** 76 * Returns the distance tolerance for changing LOD. 77 * 78 * @return the distance tolerance for changing LOD. 79 * 80 * @see #setDistTolerance(float) 81 */ 82 public float getDistTolerance() { 83 return distTolerance; 84 } 85 86 /** 87 * Specifies the distance tolerance for changing the LOD level on the geometry. 88 * The LOD level will only get changed if the geometry has moved this 89 * distance beyond the current LOD level. 90 * 91 * @param distTolerance distance tolerance for changing LOD 92 */ 93 public void setDistTolerance(float distTolerance) { 94 this.distTolerance = distTolerance; 95 } 96 97 /** 98 * Returns the triangles per pixel value. 99 * 100 * @return the triangles per pixel value. 101 * 102 * @see #setTrisPerPixel(float) 103 */ 104 public float getTrisPerPixel() { 105 return trisPerPixel; 106 } 107 108 /** 109 * Sets the triangles per pixel value. 110 * The <code>LodControl</code> will use this value as an error metric 111 * to determine which LOD level to use based on the geometry's 112 * area on the screen. 113 * 114 * @param trisPerPixel triangles per pixel 115 */ 116 public void setTrisPerPixel(float trisPerPixel) { 117 this.trisPerPixel = trisPerPixel; 118 } 119 120 @Override 121 public void setSpatial(Spatial spatial){ 122 if (!(spatial instanceof Geometry)) 123 throw new IllegalArgumentException("LodControl can only be attached to Geometry!"); 124 125 super.setSpatial(spatial); 126 Geometry geom = (Geometry) spatial; 127 Mesh mesh = geom.getMesh(); 128 numLevels = mesh.getNumLodLevels(); 129 numTris = new int[numLevels]; 130 for (int i = numLevels - 1; i >= 0; i--) 131 numTris[i] = mesh.getTriangleCount(i); 132 } 133 134 public Control cloneForSpatial(Spatial spatial) { 135 try { 136 LodControl clone = (LodControl) super.clone(); 137 clone.lastDistance = 0; 138 clone.lastLevel = 0; 139 clone.numTris = numTris != null ? numTris.clone() : null; 140 return clone; 141 } catch (CloneNotSupportedException ex) { 142 throw new AssertionError(); 143 } 144 } 145 146 @Override 147 protected void controlUpdate(float tpf) { 148 } 149 150 protected void controlRender(RenderManager rm, ViewPort vp){ 151 BoundingVolume bv = spatial.getWorldBound(); 152 153 Camera cam = vp.getCamera(); 154 float atanNH = FastMath.atan(cam.getFrustumNear() * cam.getFrustumTop()); 155 float ratio = (FastMath.PI / (8f * atanNH)); 156 float newDistance = bv.distanceTo(vp.getCamera().getLocation()) / ratio; 157 int level; 158 159 if (Math.abs(newDistance - lastDistance) <= distTolerance) 160 level = lastLevel; // we haven't moved relative to the model, send the old measurement back. 161 else if (lastDistance > newDistance && lastLevel == 0) 162 level = lastLevel; // we're already at the lowest setting and we just got closer to the model, no need to keep trying. 163 else if (lastDistance < newDistance && lastLevel == numLevels - 1) 164 level = lastLevel; // we're already at the highest setting and we just got further from the model, no need to keep trying. 165 else{ 166 lastDistance = newDistance; 167 168 // estimate area of polygon via bounding volume 169 float area = AreaUtils.calcScreenArea(bv, lastDistance, cam.getWidth()); 170 float trisToDraw = area * trisPerPixel; 171 level = numLevels - 1; 172 for (int i = numLevels; --i >= 0;){ 173 if (trisToDraw - numTris[i] < 0){ 174 break; 175 } 176 level = i; 177 } 178 lastLevel = level; 179 } 180 181 spatial.setLodLevel(level); 182 } 183 184 @Override 185 public void write(JmeExporter ex) throws IOException{ 186 super.write(ex); 187 OutputCapsule oc = ex.getCapsule(this); 188 oc.write(trisPerPixel, "trisPerPixel", 1f); 189 oc.write(distTolerance, "distTolerance", 1f); 190 oc.write(numLevels, "numLevels", 0); 191 oc.write(numTris, "numTris", null); 192 } 193 194 @Override 195 public void read(JmeImporter im) throws IOException{ 196 super.read(im); 197 InputCapsule ic = im.getCapsule(this); 198 trisPerPixel = ic.readFloat("trisPerPixel", 1f); 199 distTolerance = ic.readFloat("distTolerance", 1f); 200 numLevels = ic.readInt("numLevels", 0); 201 numTris = ic.readIntArray("numTris", null); 202 } 203 204 } 205