PageRenderTime 66ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java

https://bitbucket.org/grimm/rpg-tools
Java | 407 lines | 267 code | 64 blank | 76 comment | 43 complexity | f2435907922b561ff88c40754fdf2fa1 MD5 | raw file
  1. /*
  2. * Copyright (c) 2009-2012 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.terrain.geomipmap;
  33. import com.jme3.export.InputCapsule;
  34. import com.jme3.export.JmeExporter;
  35. import com.jme3.export.JmeImporter;
  36. import com.jme3.export.OutputCapsule;
  37. import com.jme3.math.Vector3f;
  38. import com.jme3.renderer.Camera;
  39. import com.jme3.renderer.RenderManager;
  40. import com.jme3.renderer.ViewPort;
  41. import com.jme3.scene.Node;
  42. import com.jme3.scene.Spatial;
  43. import com.jme3.scene.control.AbstractControl;
  44. import com.jme3.scene.control.Control;
  45. import com.jme3.terrain.Terrain;
  46. import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
  47. import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
  48. import java.io.IOException;
  49. import java.util.ArrayList;
  50. import java.util.HashMap;
  51. import java.util.List;
  52. import java.util.concurrent.Callable;
  53. import java.util.concurrent.ExecutionException;
  54. import java.util.concurrent.ExecutorService;
  55. import java.util.concurrent.Executors;
  56. import java.util.concurrent.Future;
  57. import java.util.concurrent.ThreadFactory;
  58. import java.util.concurrent.atomic.AtomicBoolean;
  59. import java.util.logging.Level;
  60. import java.util.logging.Logger;
  61. /**
  62. * Tells the terrain to update its Level of Detail.
  63. * It needs the cameras to do this, and there could possibly
  64. * be several cameras in the scene, so it accepts a list
  65. * of cameras.
  66. * NOTE: right now it just uses the first camera passed in,
  67. * in the future it will use all of them to determine what
  68. * LOD to set.
  69. *
  70. * This control serializes, but it does not save the Camera reference.
  71. * This camera reference has to be manually added in when you load the
  72. * terrain to the scene!
  73. *
  74. * @author Brent Owens
  75. */
  76. public class TerrainLodControl extends AbstractControl {
  77. private Terrain terrain;
  78. protected List<Camera> cameras;
  79. private List<Vector3f> cameraLocations = new ArrayList<Vector3f>();
  80. protected LodCalculator lodCalculator;
  81. private boolean hasResetLod = false; // used when enabled is set to false
  82. private HashMap<String,UpdatedTerrainPatch> updatedPatches;
  83. private final Object updatePatchesLock = new Object();
  84. protected List<Vector3f> lastCameraLocations; // used for LOD calc
  85. private AtomicBoolean lodCalcRunning = new AtomicBoolean(false);
  86. private int lodOffCount = 0;
  87. protected ExecutorService executor;
  88. protected Future<HashMap<String, UpdatedTerrainPatch>> indexer;
  89. private boolean forceUpdate = true;
  90. public TerrainLodControl() {
  91. }
  92. public TerrainLodControl(Terrain terrain, Camera camera) {
  93. List<Camera> cams = new ArrayList<Camera>();
  94. cams.add(camera);
  95. this.terrain = terrain;
  96. this.cameras = cams;
  97. lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator
  98. }
  99. /**
  100. * Only uses the first camera right now.
  101. * @param terrain to act upon (must be a Spatial)
  102. * @param cameras one or more cameras to reference for LOD calc
  103. */
  104. public TerrainLodControl(Terrain terrain, List<Camera> cameras) {
  105. this.terrain = terrain;
  106. this.cameras = cameras;
  107. lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator
  108. }
  109. @Override
  110. protected void controlRender(RenderManager rm, ViewPort vp) {
  111. }
  112. protected ExecutorService createExecutorService() {
  113. return Executors.newSingleThreadExecutor(new ThreadFactory() {
  114. public Thread newThread(Runnable r) {
  115. Thread th = new Thread(r);
  116. th.setName("jME Terrain Thread");
  117. th.setDaemon(true);
  118. return th;
  119. }
  120. });
  121. }
  122. @Override
  123. protected void controlUpdate(float tpf) {
  124. //list of cameras for when terrain supports multiple cameras (ie split screen)
  125. if (lodCalculator == null)
  126. return;
  127. if (!enabled) {
  128. if (!hasResetLod) {
  129. // this will get run once
  130. hasResetLod = true;
  131. lodCalculator.turnOffLod();
  132. }
  133. }
  134. if (cameras != null) {
  135. if (cameraLocations.isEmpty() && !cameras.isEmpty()) {
  136. for (Camera c : cameras) // populate them
  137. {
  138. cameraLocations.add(c.getLocation());
  139. }
  140. }
  141. updateLOD(cameraLocations, lodCalculator);
  142. }
  143. }
  144. // do all of the LOD calculations
  145. protected void updateLOD(List<Vector3f> locations, LodCalculator lodCalculator) {
  146. if(getSpatial() == null){
  147. return;
  148. }
  149. // update any existing ones that need updating
  150. updateQuadLODs();
  151. if (lodCalculator.isLodOff()) {
  152. // we want to calculate the base lod at least once
  153. if (lodOffCount == 1)
  154. return;
  155. else
  156. lodOffCount++;
  157. } else
  158. lodOffCount = 0;
  159. if (lastCameraLocations != null) {
  160. if (!forceUpdate && lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff())
  161. return; // don't update if in same spot
  162. else
  163. lastCameraLocations = cloneVectorList(locations);
  164. forceUpdate = false;
  165. }
  166. else {
  167. lastCameraLocations = cloneVectorList(locations);
  168. return;
  169. }
  170. if (isLodCalcRunning()) {
  171. return;
  172. }
  173. setLodCalcRunning(true);
  174. if (executor == null)
  175. executor = createExecutorService();
  176. prepareTerrain();
  177. UpdateLOD updateLodThread = getLodThread(locations, lodCalculator);
  178. indexer = executor.submit(updateLodThread);
  179. }
  180. /**
  181. * Force the LOD to update instantly, does not wait for the camera to move.
  182. * It will reset once it has updated.
  183. */
  184. public void forceUpdate() {
  185. this.forceUpdate = true;
  186. }
  187. protected void prepareTerrain() {
  188. TerrainQuad terrain = (TerrainQuad)getSpatial();
  189. terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely
  190. }
  191. protected UpdateLOD getLodThread(List<Vector3f> locations, LodCalculator lodCalculator) {
  192. return new UpdateLOD(locations, lodCalculator);
  193. }
  194. /**
  195. * Back on the ogl thread: update the terrain patch geometries
  196. */
  197. private void updateQuadLODs() {
  198. if (indexer != null) {
  199. if (indexer.isDone()) {
  200. try {
  201. HashMap<String, UpdatedTerrainPatch> updated = indexer.get();
  202. if (updated != null) {
  203. // do the actual geometry update here
  204. for (UpdatedTerrainPatch utp : updated.values()) {
  205. utp.updateAll();
  206. }
  207. }
  208. } catch (InterruptedException ex) {
  209. Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex);
  210. } catch (ExecutionException ex) {
  211. Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex);
  212. } finally {
  213. indexer = null;
  214. }
  215. }
  216. }
  217. }
  218. private boolean lastCameraLocationsTheSame(List<Vector3f> locations) {
  219. boolean theSame = true;
  220. for (Vector3f l : locations) {
  221. for (Vector3f v : lastCameraLocations) {
  222. if (!v.equals(l) ) {
  223. theSame = false;
  224. return false;
  225. }
  226. }
  227. }
  228. return theSame;
  229. }
  230. protected synchronized boolean isLodCalcRunning() {
  231. return lodCalcRunning.get();
  232. }
  233. protected synchronized void setLodCalcRunning(boolean running) {
  234. lodCalcRunning.set(running);
  235. }
  236. private List<Vector3f> cloneVectorList(List<Vector3f> locations) {
  237. List<Vector3f> cloned = new ArrayList<Vector3f>();
  238. for(Vector3f l : locations)
  239. cloned.add(l.clone());
  240. return cloned;
  241. }
  242. public Control cloneForSpatial(Spatial spatial) {
  243. if (spatial instanceof Terrain) {
  244. List<Camera> cameraClone = new ArrayList<Camera>();
  245. if (cameras != null) {
  246. for (Camera c : cameras) {
  247. cameraClone.add(c);
  248. }
  249. }
  250. TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameraClone);
  251. cloned.setLodCalculator(lodCalculator.clone());
  252. return cloned;
  253. }
  254. return null;
  255. }
  256. public void setCamera(Camera camera) {
  257. List<Camera> cams = new ArrayList<Camera>();
  258. cams.add(camera);
  259. setCameras(cams);
  260. }
  261. public void setCameras(List<Camera> cameras) {
  262. this.cameras = cameras;
  263. cameraLocations.clear();
  264. for (Camera c : cameras) {
  265. cameraLocations.add(c.getLocation());
  266. }
  267. }
  268. @Override
  269. public void setSpatial(Spatial spatial) {
  270. super.setSpatial(spatial);
  271. if (spatial instanceof Terrain) {
  272. this.terrain = (Terrain) spatial;
  273. }
  274. }
  275. public void setTerrain(Terrain terrain) {
  276. this.terrain = terrain;
  277. }
  278. public LodCalculator getLodCalculator() {
  279. return lodCalculator;
  280. }
  281. public void setLodCalculator(LodCalculator lodCalculator) {
  282. this.lodCalculator = lodCalculator;
  283. }
  284. @Override
  285. public void setEnabled(boolean enabled) {
  286. this.enabled = enabled;
  287. if (!enabled) {
  288. // reset the lod levels to max detail for the terrain
  289. hasResetLod = false;
  290. } else {
  291. hasResetLod = true;
  292. lodCalculator.turnOnLod();
  293. }
  294. }
  295. /**
  296. * Calculates the LOD of all child terrain patches.
  297. */
  298. protected class UpdateLOD implements Callable<HashMap<String,UpdatedTerrainPatch>> {
  299. protected List<Vector3f> camLocations;
  300. protected LodCalculator lodCalculator;
  301. protected UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {
  302. this.camLocations = camLocations;
  303. this.lodCalculator = lodCalculator;
  304. }
  305. public HashMap<String, UpdatedTerrainPatch> call() throws Exception {
  306. //long start = System.currentTimeMillis();
  307. //if (isLodCalcRunning()) {
  308. // return null;
  309. //}
  310. setLodCalcRunning(true);
  311. TerrainQuad terrainQuad = (TerrainQuad)getSpatial();
  312. // go through each patch and calculate its LOD based on camera distance
  313. HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();
  314. boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here
  315. if (!lodChanged) {
  316. // not worth updating anything else since no one's LOD changed
  317. setLodCalcRunning(false);
  318. return null;
  319. }
  320. // then calculate its neighbour LOD values for seaming in the shader
  321. terrainQuad.findNeighboursLod(updated);
  322. terrainQuad.fixEdges(updated); // 'updated' can get added to here
  323. terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod());
  324. //setUpdateQuadLODs(updated); // set back to main ogl thread
  325. setLodCalcRunning(false);
  326. return updated;
  327. }
  328. }
  329. @Override
  330. public void write(JmeExporter ex) throws IOException {
  331. super.write(ex);
  332. OutputCapsule oc = ex.getCapsule(this);
  333. oc.write((Node)terrain, "terrain", null);
  334. oc.write(lodCalculator, "lodCalculator", null);
  335. }
  336. @Override
  337. public void read(JmeImporter im) throws IOException {
  338. super.read(im);
  339. InputCapsule ic = im.getCapsule(this);
  340. terrain = (Terrain) ic.readSavable("terrain", null);
  341. lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator());
  342. }
  343. }