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.
empirephoenix-patch-1
javasabr 8 years ago committed by Rémy Bouquet
parent 6e07a214c6
commit 15fd80d67f
  1. 108
      jme3-terrain/src/main/java/com/jme3/terrain/executor/TerrainExecutorService.java
  2. 72
      jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java
  3. 4
      jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java
  4. 417
      jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.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<ExecutorService> constructor = new Callable<ExecutorService>() {
@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<ExecutorService> constructor) {
TerrainExecutorService.constructor = constructor;
}
/**
* https://stackoverflow.com/questions/29883403/double-checked-locking-without-volatile
* <p>
* 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 <T> Future<T> submit(final Callable<T> task) {
return executorService.submit(task);
}
public <T> Future<T> 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);
}
}

@ -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<TerrainQuad> terrains = new ArrayList<TerrainQuad>();
private List<TerrainQuad> addedTerrains = new ArrayList<TerrainQuad>();
private List<TerrainQuad> removedTerrains = new ArrayList<TerrainQuad>();
public MultiTerrainLodControl(List<Camera> cameras) {
this.cameras = cameras;
lodCalculator = new DistanceLodCalculator(65, 2.7f);
private SafeArrayList<TerrainQuad> terrains;
private List<TerrainQuad> addedTerrains;
private List<TerrainQuad> 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<Camera> cams = new ArrayList<Camera>();
cams.add(camera);
this.cameras = cams;
lodCalculator = new DistanceLodCalculator(65, 2.7f);
public MultiTerrainLodControl(final Terrain terrain, final List<Camera> 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<Vector3f> locations, LodCalculator lodCalculator) {
protected UpdateLOD createLodUpdateTask(final SafeArrayList<Vector3f> 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<Vector3f> camLocations, LodCalculator lodCalculator) {
protected UpdateMultiLOD(final SafeArrayList<Vector3f> camLocations, final LodCalculator lodCalculator) {
super(camLocations, lodCalculator);
}
@Override
public HashMap<String, UpdatedTerrainPatch> call() throws Exception {
setLodCalcRunning(true);
HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();
HashMap<String, UpdatedTerrainPatch> 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;
}

@ -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<Vector3f> locations, LodCalculator lodCalculator) {
protected void updateLOD(SafeArrayList<Vector3f> locations, LodCalculator lodCalculator) {
TerrainGrid terrainGrid = (TerrainGrid)getSpatial();
// for now, only the first camera is handled.

@ -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<Camera> cameras;
private List<Vector3f> cameraLocations = new ArrayList<Vector3f>();
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<Camera> cameras;
protected SafeArrayList<Vector3f> cameraLocations;
protected SafeArrayList<Vector3f> lastCameraLocations;
private HashMap<String,UpdatedTerrainPatch> updatedPatches;
private final Object updatePatchesLock = new Object();
protected AtomicBoolean lodCalcRunning;
protected List<Vector3f> 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<HashMap<String, UpdatedTerrainPatch>> 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<Camera> cams = new ArrayList<Camera>();
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<Camera> cameras) {
this.terrain = terrain;
this.cameras = cameras;
lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator
public TerrainLodControl(final Terrain terrain, final List<Camera> 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<Vector3f> 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<Vector3f> 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<Vector3f> 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<Vector3f> locations, LodCalculator lodCalculator) {
protected UpdateLOD createLodUpdateTask(final SafeArrayList<Vector3f> 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<String, UpdatedTerrainPatch> 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<Vector3f> locations) {
boolean theSame = true;
for (Vector3f l : locations) {
for (Vector3f v : lastCameraLocations) {
if (!v.equals(l) ) {
theSame = false;
return false;
try {
final HashMap<String, UpdatedTerrainPatch> 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<Vector3f> cloneVectorList(SafeArrayList<Vector3f> locations) {
protected synchronized void setLodCalcRunning(boolean running) {
lodCalcRunning.set(running);
}
final SafeArrayList<Vector3f> cloned = new SafeArrayList<>(Vector3f.class, locations.size());
for (final Vector3f location : locations.getArray()) {
cloned.add(location.clone());
}
private List<Vector3f> cloneVectorList(List<Vector3f> locations) {
List<Vector3f> cloned = new ArrayList<Vector3f>();
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<Camera> cameraClone = new ArrayList<Camera>();
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<Camera> cams = new ArrayList<Camera>();
cams.add(camera);
setCameras(cams);
public void setCamera(final Camera camera) {
this.cameras.clear();
this.cameras.add(camera);
}
public void setCameras(List<Camera> cameras) {
this.cameras = cameras;
cameraLocations.clear();
for (Camera c : cameras) {
cameraLocations.add(c.getLocation());
}
public void setCameras(final List<Camera> 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<HashMap<String,UpdatedTerrainPatch>> {
protected List<Vector3f> camLocations;
protected LodCalculator lodCalculator;
protected class UpdateLOD implements Callable<HashMap<String, UpdatedTerrainPatch>> {
protected UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {
protected final SafeArrayList<Vector3f> camLocations;
protected final LodCalculator lodCalculator;
protected UpdateLOD(final SafeArrayList<Vector3f> camLocations, final LodCalculator lodCalculator) {
this.camLocations = camLocations;
this.lodCalculator = lodCalculator;
}
public HashMap<String, UpdatedTerrainPatch> 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<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();
boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here
HashMap<String, UpdatedTerrainPatch> 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);
}
}

Loading…
Cancel
Save