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); } - }