From 15fd80d67f2c1cbc701b7774a36ada076f80d418 Mon Sep 17 00:00:00 2001 From: javasabr Date: Sun, 6 Aug 2017 12:41:58 +0300 Subject: [PATCH] Some improvements with terrain LOD control: 1. Added the new option to use camera from render view port(default false) 2. Removed unnecessary synchronize blocks 3. Replaced each control own executor to global terrain executor 4. Removed unused things 5. Reduced memory allocations 6. Fixed the issue with cloning. --- .../executor/TerrainExecutorService.java | 108 +++++ .../geomipmap/MultiTerrainLodControl.java | 72 ++- .../geomipmap/TerrainGridLodControl.java | 4 +- .../terrain/geomipmap/TerrainLodControl.java | 417 ++++++++++-------- 4 files changed, 401 insertions(+), 200 deletions(-) create mode 100644 jme3-terrain/src/main/java/com/jme3/terrain/executor/TerrainExecutorService.java diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/executor/TerrainExecutorService.java b/jme3-terrain/src/main/java/com/jme3/terrain/executor/TerrainExecutorService.java new file mode 100644 index 000000000..b89864fce --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/executor/TerrainExecutorService.java @@ -0,0 +1,108 @@ +package com.jme3.terrain.executor; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The class to provide single executor service to run background tasks of terrain staff. + * + * @author JavaSaBr + */ +public class TerrainExecutorService { + + private static final Runtime RUNTIME = Runtime.getRuntime(); + + /** + * The constructor of the terrain executor service. + */ + private static volatile Callable constructor = new Callable() { + + @Override + public ExecutorService call() throws Exception { + return Executors.newFixedThreadPool(RUNTIME.availableProcessors(), new ThreadFactory() { + + private final AtomicInteger counter = new AtomicInteger(-1); + + @Override + public Thread newThread(final Runnable task) { + final Thread thread = new Thread(task); + thread.setName("jME3 Terrain Thread [" + counter.incrementAndGet() + "]"); + thread.setDaemon(true); + return thread; + } + }); + } + }; + + /** + * Set a new constructor of executor service to provide other implementation. + * + * @param constructor the constructor. + */ + private static void setConstructor(final Callable constructor) { + TerrainExecutorService.constructor = constructor; + } + + /** + * https://stackoverflow.com/questions/29883403/double-checked-locking-without-volatile + *

+ * This suggestion is of Aleksey Shipilev + */ + private static class LazyInitializer { + public final TerrainExecutorService instance; + public LazyInitializer(final TerrainExecutorService instance) { + this.instance = instance; + } + } + + /** + * The lazy singleton. + */ + private static LazyInitializer initializer; + + public static TerrainExecutorService getInstance() { + + LazyInitializer lazy = initializer; + + if (lazy == null) { // check 1 + synchronized (TerrainExecutorService.class) { + lazy = initializer; + if (lazy == null) { // check2 + lazy = new LazyInitializer(new TerrainExecutorService()); + initializer = lazy; + } + } + } + + return lazy.instance; + } + + /** + * The implementation of executor service. + */ + private final ExecutorService executorService; + + private TerrainExecutorService() { + try { + this.executorService = constructor.call(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + public Future submit(final Callable task) { + return executorService.submit(task); + } + + public Future submit(final Runnable task, final T result) { + return executorService.submit(task, result); + } + + public Future submit(final Runnable task) { + return executorService.submit(task); + } + + public void execute(final Runnable command) { + executorService.execute(command); + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java index 995bc8f13..541f7a997 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java @@ -33,8 +33,11 @@ package com.jme3.terrain.geomipmap; import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; +import com.jme3.terrain.Terrain; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import com.jme3.util.SafeArrayList; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -48,21 +51,41 @@ import java.util.List; * @author Brent Owens */ public class MultiTerrainLodControl extends TerrainLodControl { - - List terrains = new ArrayList(); - private List addedTerrains = new ArrayList(); - private List removedTerrains = new ArrayList(); - public MultiTerrainLodControl(List cameras) { - this.cameras = cameras; - lodCalculator = new DistanceLodCalculator(65, 2.7f); + private SafeArrayList terrains; + + private List addedTerrains; + private List removedTerrains; + + public MultiTerrainLodControl() { + terrains = new SafeArrayList<>(TerrainQuad.class); + removedTerrains = new ArrayList<>(); + addedTerrains = new ArrayList<>(); + } + + public MultiTerrainLodControl(final Terrain terrain) { + this(); + setTerrain(terrain); + } + + public MultiTerrainLodControl(final Camera camera) { + this(); + setCamera(camera); + } + + public MultiTerrainLodControl(final Terrain terrain, final Camera camera) { + this(terrain); + setCamera(camera); } - public MultiTerrainLodControl(Camera camera) { - List cams = new ArrayList(); - cams.add(camera); - this.cameras = cams; - lodCalculator = new DistanceLodCalculator(65, 2.7f); + public MultiTerrainLodControl(final Terrain terrain, final List cameras) { + this(terrain); + setCameras(cameras); + } + + @Override + protected DistanceLodCalculator makeLodCalculator() { + return new DistanceLodCalculator(65, 2.7f); } /** @@ -84,7 +107,8 @@ public class MultiTerrainLodControl extends TerrainLodControl { } @Override - protected UpdateLOD getLodThread(List locations, LodCalculator lodCalculator) { + protected UpdateLOD createLodUpdateTask(final SafeArrayList locations, + final LodCalculator lodCalculator) { return new UpdateMultiLOD(locations, lodCalculator); } @@ -92,8 +116,9 @@ public class MultiTerrainLodControl extends TerrainLodControl { protected void prepareTerrain() { if (!addedTerrains.isEmpty()) { for (TerrainQuad t : addedTerrains) { - if (!terrains.contains(t)) + if (!terrains.contains(t)) { terrains.add(t); + } } addedTerrains.clear(); } @@ -103,8 +128,10 @@ public class MultiTerrainLodControl extends TerrainLodControl { removedTerrains.clear(); } - for (TerrainQuad terrain : terrains) - terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely + for (TerrainQuad terrain : terrains.getArray()) { + // cache the terrain's world transforms so they can be accessed on the separate thread safely + terrain.cacheTerrainTransforms(); + } } /** @@ -112,18 +139,15 @@ public class MultiTerrainLodControl extends TerrainLodControl { * multiple terrains. */ protected class UpdateMultiLOD extends UpdateLOD { - - - protected UpdateMultiLOD(List camLocations, LodCalculator lodCalculator) { + + protected UpdateMultiLOD(final SafeArrayList camLocations, final LodCalculator lodCalculator) { super(camLocations, lodCalculator); } @Override public HashMap call() throws Exception { - - setLodCalcRunning(true); - - HashMap updated = new HashMap(); + + HashMap updated = new HashMap<>(); for (TerrainQuad terrainQuad : terrains) { // go through each patch and calculate its LOD based on camera distance @@ -146,7 +170,7 @@ public class MultiTerrainLodControl extends TerrainLodControl { } //setUpdateQuadLODs(updated); // set back to main ogl thread - setLodCalcRunning(false); + lodCalcRunning.set(false); return updated; } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java index 65a209be3..20a088901 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java @@ -35,7 +35,7 @@ import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.terrain.Terrain; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; -import java.util.List; +import com.jme3.util.SafeArrayList; /** * Updates grid offsets and cell positions. @@ -50,7 +50,7 @@ public class TerrainGridLodControl extends TerrainLodControl { } @Override - protected void updateLOD(List locations, LodCalculator lodCalculator) { + protected void updateLOD(SafeArrayList locations, LodCalculator lodCalculator) { TerrainGrid terrainGrid = (TerrainGrid)getSpatial(); // for now, only the first camera is handled. diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java index d551ff4e5..d38a32878 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java @@ -44,20 +44,19 @@ import com.jme3.scene.Spatial; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.Control; import com.jme3.terrain.Terrain; +import com.jme3.terrain.executor.TerrainExecutorService; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import com.jme3.util.SafeArrayList; import com.jme3.util.clone.Cloner; -import com.jme3.util.clone.JmeCloneable; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -84,32 +83,68 @@ import java.util.logging.Logger; */ public class TerrainLodControl extends AbstractControl { - private Terrain terrain; - protected List cameras; - private List cameraLocations = new ArrayList(); - protected LodCalculator lodCalculator; - private boolean hasResetLod = false; // used when enabled is set to false + /** + * The list of cameras for when terrain supports multiple cameras (ie split screen) + */ + protected SafeArrayList cameras; + protected SafeArrayList cameraLocations; + protected SafeArrayList lastCameraLocations; - private HashMap updatedPatches; - private final Object updatePatchesLock = new Object(); + protected AtomicBoolean lodCalcRunning; - protected List lastCameraLocations; // used for LOD calc - private AtomicBoolean lodCalcRunning = new AtomicBoolean(false); - private int lodOffCount = 0; + /** + * The previous location of {@link #camera}. + */ + protected Vector3f previousCameraLocation; + + /** + * The camera from render view port. + */ + protected Camera camera; - protected ExecutorService executor; + protected Terrain terrain; + protected LodCalculator lodCalculator; protected Future> indexer; - private boolean forceUpdate = true; + + private int lodOffCount; + + /** + * The flag of using a camera from render viewport instead cameras from {@link #cameras}. + */ + protected boolean useRenderCamera; + + protected boolean forceUpdate; + protected boolean hasResetLod; // used when enabled is set to false public TerrainLodControl() { + hasResetLod = false; + forceUpdate = true; + previousCameraLocation = new Vector3f(); + cameras = new SafeArrayList<>(Camera.class); + cameraLocations = new SafeArrayList<>(Vector3f.class); + lastCameraLocations = new SafeArrayList<>(Vector3f.class); + lodCalcRunning = new AtomicBoolean(false); + lodOffCount = 0; + lodCalculator = makeLodCalculator(); // a default calculator } - public TerrainLodControl(Terrain terrain, Camera camera) { - List cams = new ArrayList(); - cams.add(camera); + protected DistanceLodCalculator makeLodCalculator() { + return new DistanceLodCalculator(65, 2.7f); + } + + public TerrainLodControl(final Terrain terrain) { + this(); this.terrain = terrain; - this.cameras = cams; - lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator + } + + public TerrainLodControl(final Camera camera) { + this(); + setCamera(camera); + } + + public TerrainLodControl(final Terrain terrain, final Camera camera) { + this(terrain); + setCamera(camera); } /** @@ -117,41 +152,44 @@ public class TerrainLodControl extends AbstractControl { * @param terrain to act upon (must be a Spatial) * @param cameras one or more cameras to reference for LOD calc */ - public TerrainLodControl(Terrain terrain, List cameras) { - this.terrain = terrain; - this.cameras = cameras; - lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator + public TerrainLodControl(final Terrain terrain, final List cameras) { + this(terrain); + setCameras(cameras); } - @Override - protected void controlRender(RenderManager rm, ViewPort vp) { + /** + * @param useRenderCamera true if need to use a camera from render view port. + */ + public void setUseRenderCamera(final boolean useRenderCamera) { + this.useRenderCamera = useRenderCamera; } /** - * Set your own custom executor to be used. The control will use - * this instead of creating its own. + * @return true if need to use a camera from render view port. */ - public void setExecutor(ExecutorService executor) { - this.executor = executor; + public boolean isUseRenderCamera() { + return useRenderCamera; } - protected ExecutorService createExecutorService() { - return Executors.newSingleThreadExecutor(new ThreadFactory() { - public Thread newThread(Runnable r) { - Thread th = new Thread(r); - th.setName("jME3 Terrain Thread"); - th.setDaemon(true); - return th; - } - }); + @Override + protected void controlRender(final RenderManager rm, final ViewPort vp) { + + if (!isUseRenderCamera()) { + return; + } else if (camera == vp.getCamera()) { + return; + } + + camera = vp.getCamera(); + previousCameraLocation.set(camera.getLocation()); } @Override protected void controlUpdate(float tpf) { - //list of cameras for when terrain supports multiple cameras (ie split screen) - if (lodCalculator == null) + if (lodCalculator == null) { return; + } if (!enabled) { if (!hasResetLod) { @@ -161,12 +199,26 @@ public class TerrainLodControl extends AbstractControl { } } - if (cameras != null) { - cameraLocations.clear(); - for (Camera c : cameras) // populate them - { - cameraLocations.add(c.getLocation()); + // if we use a camera from render + if (isUseRenderCamera()) { + updateLOD(lodCalculator); + } + // if we use set cameras + else if (!cameras.isEmpty()) { + + // need to have count of positions the same with count of cameras + if (cameraLocations.size() != cameras.size()) { + cameraLocations.clear(); + for (int i = 0; i < cameras.size(); i++) { + cameraLocations.add(new Vector3f()); + } + } + + // we need to update current camera positions + for (int i = 0; i < cameras.size(); i++) { + cameraLocations.get(i).set(cameras.get(i).getLocation()); } + updateLOD(cameraLocations, lodCalculator); } } @@ -176,53 +228,110 @@ public class TerrainLodControl extends AbstractControl { * It will clear up any threads it had. */ public void detachAndCleanUpControl() { - if (executor != null) - executor.shutdownNow(); + + if (indexer != null) { + indexer.cancel(true); + indexer = null; + } + getSpatial().removeControl(this); } // do all of the LOD calculations - protected void updateLOD(List locations, LodCalculator lodCalculator) { - if(getSpatial() == null){ + protected void updateLOD(final LodCalculator lodCalculator) { + + if (getSpatial() == null || camera == null) { return; } // update any existing ones that need updating updateQuadLODs(); - if (lodCalculator.isLodOff()) { - // we want to calculate the base lod at least once - if (lodOffCount == 1) - return; - else - lodOffCount++; - } else - lodOffCount = 0; + if (updateLodOffCount(lodCalculator)) { + return; + } + + final Vector3f currentLocation = camera.getLocation(); - if (lastCameraLocations != null) { - if (!forceUpdate && lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff()) - return; // don't update if in same spot - else - lastCameraLocations = cloneVectorList(locations); - forceUpdate = false; + if (!forceUpdate && previousCameraLocation.equals(currentLocation) && !lodCalculator.isLodOff()) { + return; // don't update if in same spot + } else { + previousCameraLocation.set(currentLocation); } - else { - lastCameraLocations = cloneVectorList(locations); + + forceUpdate = false; + + if (!lodCalcRunning.compareAndSet(false, true)) { + return; + } + + prepareTerrain(); + + final SafeArrayList locations = new SafeArrayList<>(Vector3f.class, 1); + locations.add(currentLocation); + + final TerrainExecutorService executorService = TerrainExecutorService.getInstance(); + indexer = executorService.submit(createLodUpdateTask(locations, lodCalculator)); + } + + // do all of the LOD calculations + protected void updateLOD(final SafeArrayList locations, final LodCalculator lodCalculator) { + + if (getSpatial() == null || locations.isEmpty()) { return; } - if (isLodCalcRunning()) { + // update any existing ones that need updating + updateQuadLODs(); + + if (updateLodOffCount(lodCalculator)) { return; } - setLodCalcRunning(true); - if (executor == null) - executor = createExecutorService(); + if (!forceUpdate && locations.equals(lastCameraLocations) && !lodCalculator.isLodOff()) { + return; // don't update if in same spot + } else { + + // need to have count of last camera locations the same with count of locations + if (lastCameraLocations.size() != locations.size()) { + lastCameraLocations.clear(); + for (int i = 0; i < locations.size(); i++) { + lastCameraLocations.add(new Vector3f()); + } + } + + // we need to update last camera locations to current + for (int i = 0; i < locations.size(); i++) { + lastCameraLocations.get(i).set(locations.get(i)); + } + } + + forceUpdate = false; + + if (!lodCalcRunning.compareAndSet(false, true)) { + return; + } prepareTerrain(); - UpdateLOD updateLodThread = getLodThread(locations, lodCalculator); - indexer = executor.submit(updateLodThread); + final TerrainExecutorService executorService = TerrainExecutorService.getInstance(); + indexer = executorService.submit(createLodUpdateTask(cloneVectorList(locations), lodCalculator)); + } + + protected boolean updateLodOffCount(final LodCalculator lodCalculator) { + + if (lodCalculator.isLodOff()) { + // we want to calculate the base lod at least once + if (lodOffCount == 1) { + return true; + } else { + lodOffCount++; + } + } else { + lodOffCount = 0; + } + + return false; } /** @@ -234,11 +343,12 @@ public class TerrainLodControl extends AbstractControl { } protected void prepareTerrain() { - TerrainQuad terrain = (TerrainQuad)getSpatial(); - terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely + TerrainQuad terrain = (TerrainQuad) getSpatial(); + // cache the terrain's world transforms so they can be accessed on the separate thread safely + terrain.cacheTerrainTransforms(); } - protected UpdateLOD getLodThread(List locations, LodCalculator lodCalculator) { + protected UpdateLOD createLodUpdateTask(final SafeArrayList locations, final LodCalculator lodCalculator) { return new UpdateLOD(locations, lodCalculator); } @@ -246,115 +356,78 @@ public class TerrainLodControl extends AbstractControl { * Back on the ogl thread: update the terrain patch geometries */ private void updateQuadLODs() { - if (indexer != null) { - if (indexer.isDone()) { - try { - - HashMap updated = indexer.get(); - if (updated != null) { - // do the actual geometry update here - for (UpdatedTerrainPatch utp : updated.values()) { - utp.updateAll(); - } - } - - } catch (InterruptedException ex) { - Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex); - } catch (ExecutionException ex) { - Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex); - } finally { - indexer = null; - } - } + + if (indexer == null || !indexer.isDone()) { + return; } - } - private boolean lastCameraLocationsTheSame(List locations) { - boolean theSame = true; - for (Vector3f l : locations) { - for (Vector3f v : lastCameraLocations) { - if (!v.equals(l) ) { - theSame = false; - return false; + try { + + final HashMap updated = indexer.get(); + if (updated != null) { + // do the actual geometry update here + for (final UpdatedTerrainPatch utp : updated.values()) { + utp.updateAll(); } } + + } catch (final InterruptedException | ExecutionException ex) { + Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex); + } finally { + indexer = null; } - return theSame; } - protected synchronized boolean isLodCalcRunning() { - return lodCalcRunning.get(); - } + private SafeArrayList cloneVectorList(SafeArrayList locations) { - protected synchronized void setLodCalcRunning(boolean running) { - lodCalcRunning.set(running); - } + final SafeArrayList cloned = new SafeArrayList<>(Vector3f.class, locations.size()); + + for (final Vector3f location : locations.getArray()) { + cloned.add(location.clone()); + } - private List cloneVectorList(List locations) { - List cloned = new ArrayList(); - for(Vector3f l : locations) - cloned.add(l.clone()); return cloned; } - - - - @Override public Object jmeClone() { - if (spatial instanceof Terrain) { - TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameras); - cloned.setLodCalculator(lodCalculator.clone()); - cloned.spatial = spatial; - return cloned; + try { + return super.clone(); + } catch (final CloneNotSupportedException e) { + throw new RuntimeException(e); } - return null; } @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(final Cloner cloner, final Object original) { super.cloneFields(cloner, original); this.lodCalculator = cloner.clone(lodCalculator); - - try { - // Not deep clone of the cameras themselves - this.cameras = cloner.javaClone(cameras); - } catch( CloneNotSupportedException e ) { - throw new RuntimeException("Error cloning", e); - } + this.cameras = new SafeArrayList<>(Camera.class, cameras); + this.cameraLocations = new SafeArrayList<>(Vector3f.class); + this.lastCameraLocations = new SafeArrayList<>(Vector3f.class); + this.lodCalcRunning = new AtomicBoolean(); + this.previousCameraLocation = new Vector3f(); } - @Override - public Control cloneForSpatial(Spatial spatial) { + public Control cloneForSpatial(final Spatial spatial) { if (spatial instanceof Terrain) { - List cameraClone = new ArrayList(); - if (cameras != null) { - for (Camera c : cameras) { - cameraClone.add(c); - } - } - TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameraClone); + TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, new ArrayList<>(cameras)); cloned.setLodCalculator(lodCalculator.clone()); return cloned; } return null; } - public void setCamera(Camera camera) { - List cams = new ArrayList(); - cams.add(camera); - setCameras(cams); + public void setCamera(final Camera camera) { + this.cameras.clear(); + this.cameras.add(camera); } - public void setCameras(List cameras) { - this.cameras = cameras; - cameraLocations.clear(); - for (Camera c : cameras) { - cameraLocations.add(c.getLocation()); - } + public void setCameras(final List cameras) { + this.cameras.clear(); + this.cameras.addAll(cameras); } @Override @@ -373,7 +446,7 @@ public class TerrainLodControl extends AbstractControl { return lodCalculator; } - public void setLodCalculator(LodCalculator lodCalculator) { + public void setLodCalculator(final LodCalculator lodCalculator) { this.lodCalculator = lodCalculator; } @@ -393,64 +466,60 @@ public class TerrainLodControl extends AbstractControl { /** * Calculates the LOD of all child terrain patches. */ - protected class UpdateLOD implements Callable> { - protected List camLocations; - protected LodCalculator lodCalculator; + protected class UpdateLOD implements Callable> { - protected UpdateLOD(List camLocations, LodCalculator lodCalculator) { + protected final SafeArrayList camLocations; + protected final LodCalculator lodCalculator; + + protected UpdateLOD(final SafeArrayList camLocations, final LodCalculator lodCalculator) { this.camLocations = camLocations; this.lodCalculator = lodCalculator; } public HashMap call() throws Exception { - //long start = System.currentTimeMillis(); - //if (isLodCalcRunning()) { - // return null; - //} - setLodCalcRunning(true); - TerrainQuad terrainQuad = (TerrainQuad)getSpatial(); + TerrainQuad terrainQuad = (TerrainQuad) getSpatial(); // go through each patch and calculate its LOD based on camera distance - HashMap updated = new HashMap(); - boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here + HashMap updated = new HashMap<>(); + // 'updated' gets populated here + boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); if (!lodChanged) { // not worth updating anything else since no one's LOD changed - setLodCalcRunning(false); + lodCalcRunning.set(false); return null; } - // then calculate its neighbour LOD values for seaming in the shader terrainQuad.findNeighboursLod(updated); - - terrainQuad.fixEdges(updated); // 'updated' can get added to here - + // 'updated' can get added to here + terrainQuad.fixEdges(updated); terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod()); //setUpdateQuadLODs(updated); // set back to main ogl thread - setLodCalcRunning(false); + lodCalcRunning.set(false); return updated; } } @Override - public void write(JmeExporter ex) throws IOException { + public void write(final JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write((Node)terrain, "terrain", null); oc.write(lodCalculator, "lodCalculator", null); + oc.write(useRenderCamera, "useRenderCamera", false); } @Override - public void read(JmeImporter im) throws IOException { + public void read(final JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); terrain = (Terrain) ic.readSavable("terrain", null); lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator()); + useRenderCamera = ic.readBoolean("useRenderCamera", false); } - }