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. 66
      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.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.terrain.Terrain;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
import com.jme3.util.SafeArrayList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -49,20 +52,40 @@ import java.util.List;
*/ */
public class MultiTerrainLodControl extends TerrainLodControl { public class MultiTerrainLodControl extends TerrainLodControl {
List<TerrainQuad> terrains = new ArrayList<TerrainQuad>(); private SafeArrayList<TerrainQuad> terrains;
private List<TerrainQuad> addedTerrains = new ArrayList<TerrainQuad>();
private List<TerrainQuad> removedTerrains = new ArrayList<TerrainQuad>(); 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(List<Camera> cameras) { public MultiTerrainLodControl(final Terrain terrain, final List<Camera> cameras) {
this.cameras = cameras; this(terrain);
lodCalculator = new DistanceLodCalculator(65, 2.7f); setCameras(cameras);
} }
public MultiTerrainLodControl(Camera camera) { @Override
List<Camera> cams = new ArrayList<Camera>(); protected DistanceLodCalculator makeLodCalculator() {
cams.add(camera); return new DistanceLodCalculator(65, 2.7f);
this.cameras = cams;
lodCalculator = new DistanceLodCalculator(65, 2.7f);
} }
/** /**
@ -84,7 +107,8 @@ public class MultiTerrainLodControl extends TerrainLodControl {
} }
@Override @Override
protected UpdateLOD getLodThread(List<Vector3f> locations, LodCalculator lodCalculator) { protected UpdateLOD createLodUpdateTask(final SafeArrayList<Vector3f> locations,
final LodCalculator lodCalculator) {
return new UpdateMultiLOD(locations, lodCalculator); return new UpdateMultiLOD(locations, lodCalculator);
} }
@ -92,8 +116,9 @@ public class MultiTerrainLodControl extends TerrainLodControl {
protected void prepareTerrain() { protected void prepareTerrain() {
if (!addedTerrains.isEmpty()) { if (!addedTerrains.isEmpty()) {
for (TerrainQuad t : addedTerrains) { for (TerrainQuad t : addedTerrains) {
if (!terrains.contains(t)) if (!terrains.contains(t)) {
terrains.add(t); terrains.add(t);
}
} }
addedTerrains.clear(); addedTerrains.clear();
} }
@ -103,8 +128,10 @@ public class MultiTerrainLodControl extends TerrainLodControl {
removedTerrains.clear(); removedTerrains.clear();
} }
for (TerrainQuad terrain : terrains) for (TerrainQuad terrain : terrains.getArray()) {
terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely // cache the terrain's world transforms so they can be accessed on the separate thread safely
terrain.cacheTerrainTransforms();
}
} }
/** /**
@ -113,17 +140,14 @@ public class MultiTerrainLodControl extends TerrainLodControl {
*/ */
protected class UpdateMultiLOD extends UpdateLOD { protected class UpdateMultiLOD extends UpdateLOD {
protected UpdateMultiLOD(final SafeArrayList<Vector3f> camLocations, final LodCalculator lodCalculator) {
protected UpdateMultiLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {
super(camLocations, lodCalculator); super(camLocations, lodCalculator);
} }
@Override @Override
public HashMap<String, UpdatedTerrainPatch> call() throws Exception { public HashMap<String, UpdatedTerrainPatch> call() throws Exception {
setLodCalcRunning(true); HashMap<String, UpdatedTerrainPatch> updated = new HashMap<>();
HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();
for (TerrainQuad terrainQuad : terrains) { for (TerrainQuad terrainQuad : terrains) {
// go through each patch and calculate its LOD based on camera distance // 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 //setUpdateQuadLODs(updated); // set back to main ogl thread
setLodCalcRunning(false); lodCalcRunning.set(false);
return updated; return updated;
} }

@ -35,7 +35,7 @@ import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.terrain.Terrain; import com.jme3.terrain.Terrain;
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
import java.util.List; import com.jme3.util.SafeArrayList;
/** /**
* Updates grid offsets and cell positions. * Updates grid offsets and cell positions.
@ -50,7 +50,7 @@ public class TerrainGridLodControl extends TerrainLodControl {
} }
@Override @Override
protected void updateLOD(List<Vector3f> locations, LodCalculator lodCalculator) { protected void updateLOD(SafeArrayList<Vector3f> locations, LodCalculator lodCalculator) {
TerrainGrid terrainGrid = (TerrainGrid)getSpatial(); TerrainGrid terrainGrid = (TerrainGrid)getSpatial();
// for now, only the first camera is handled. // 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.AbstractControl;
import com.jme3.scene.control.Control; import com.jme3.scene.control.Control;
import com.jme3.terrain.Terrain; import com.jme3.terrain.Terrain;
import com.jme3.terrain.executor.TerrainExecutorService;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -84,32 +83,68 @@ import java.util.logging.Logger;
*/ */
public class TerrainLodControl extends AbstractControl { public class TerrainLodControl extends AbstractControl {
private Terrain terrain; /**
protected List<Camera> cameras; * The list of cameras for when terrain supports multiple cameras (ie split screen)
private List<Vector3f> cameraLocations = new ArrayList<Vector3f>(); */
protected LodCalculator lodCalculator; protected SafeArrayList<Camera> cameras;
private boolean hasResetLod = false; // used when enabled is set to false protected SafeArrayList<Vector3f> cameraLocations;
protected SafeArrayList<Vector3f> lastCameraLocations;
private HashMap<String,UpdatedTerrainPatch> updatedPatches; protected AtomicBoolean lodCalcRunning;
private final Object updatePatchesLock = new Object();
protected List<Vector3f> lastCameraLocations; // used for LOD calc /**
private AtomicBoolean lodCalcRunning = new AtomicBoolean(false); * The previous location of {@link #camera}.
private int lodOffCount = 0; */
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; 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() { 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) { protected DistanceLodCalculator makeLodCalculator() {
List<Camera> cams = new ArrayList<Camera>(); return new DistanceLodCalculator(65, 2.7f);
cams.add(camera); }
public TerrainLodControl(final Terrain terrain) {
this();
this.terrain = terrain; 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 terrain to act upon (must be a Spatial)
* @param cameras one or more cameras to reference for LOD calc * @param cameras one or more cameras to reference for LOD calc
*/ */
public TerrainLodControl(Terrain terrain, List<Camera> cameras) { public TerrainLodControl(final Terrain terrain, final List<Camera> cameras) {
this.terrain = terrain; this(terrain);
this.cameras = cameras; setCameras(cameras);
lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator
} }
@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 * @return true if need to use a camera from render view port.
* this instead of creating its own.
*/ */
public void setExecutor(ExecutorService executor) { public boolean isUseRenderCamera() {
this.executor = executor; return useRenderCamera;
} }
protected ExecutorService createExecutorService() { @Override
return Executors.newSingleThreadExecutor(new ThreadFactory() { protected void controlRender(final RenderManager rm, final ViewPort vp) {
public Thread newThread(Runnable r) {
Thread th = new Thread(r); if (!isUseRenderCamera()) {
th.setName("jME3 Terrain Thread"); return;
th.setDaemon(true); } else if (camera == vp.getCamera()) {
return th; return;
} }
});
camera = vp.getCamera();
previousCameraLocation.set(camera.getLocation());
} }
@Override @Override
protected void controlUpdate(float tpf) { protected void controlUpdate(float tpf) {
//list of cameras for when terrain supports multiple cameras (ie split screen)
if (lodCalculator == null) if (lodCalculator == null) {
return; return;
}
if (!enabled) { if (!enabled) {
if (!hasResetLod) { if (!hasResetLod) {
@ -161,12 +199,26 @@ public class TerrainLodControl extends AbstractControl {
} }
} }
if (cameras != null) { // if we use a camera from render
cameraLocations.clear(); if (isUseRenderCamera()) {
for (Camera c : cameras) // populate them updateLOD(lodCalculator);
{ }
cameraLocations.add(c.getLocation()); // 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); updateLOD(cameraLocations, lodCalculator);
} }
} }
@ -176,53 +228,110 @@ public class TerrainLodControl extends AbstractControl {
* It will clear up any threads it had. * It will clear up any threads it had.
*/ */
public void detachAndCleanUpControl() { public void detachAndCleanUpControl() {
if (executor != null)
executor.shutdownNow(); if (indexer != null) {
indexer.cancel(true);
indexer = null;
}
getSpatial().removeControl(this); getSpatial().removeControl(this);
} }
// do all of the LOD calculations // do all of the LOD calculations
protected void updateLOD(List<Vector3f> locations, LodCalculator lodCalculator) { protected void updateLOD(final LodCalculator lodCalculator) {
if(getSpatial() == null){
if (getSpatial() == null || camera == null) {
return; return;
} }
// update any existing ones that need updating // update any existing ones that need updating
updateQuadLODs(); updateQuadLODs();
if (lodCalculator.isLodOff()) { if (updateLodOffCount(lodCalculator)) {
// we want to calculate the base lod at least once return;
if (lodOffCount == 1) }
return;
else final Vector3f currentLocation = camera.getLocation();
lodOffCount++;
} else
lodOffCount = 0;
if (lastCameraLocations != null) { if (!forceUpdate && previousCameraLocation.equals(currentLocation) && !lodCalculator.isLodOff()) {
if (!forceUpdate && lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff()) return; // don't update if in same spot
return; // don't update if in same spot } else {
else previousCameraLocation.set(currentLocation);
lastCameraLocations = cloneVectorList(locations);
forceUpdate = false;
} }
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; return;
} }
if (isLodCalcRunning()) { // update any existing ones that need updating
updateQuadLODs();
if (updateLodOffCount(lodCalculator)) {
return; return;
} }
setLodCalcRunning(true);
if (executor == null) if (!forceUpdate && locations.equals(lastCameraLocations) && !lodCalculator.isLodOff()) {
executor = createExecutorService(); 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(); prepareTerrain();
UpdateLOD updateLodThread = getLodThread(locations, lodCalculator); final TerrainExecutorService executorService = TerrainExecutorService.getInstance();
indexer = executor.submit(updateLodThread); 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() { protected void prepareTerrain() {
TerrainQuad terrain = (TerrainQuad)getSpatial(); TerrainQuad terrain = (TerrainQuad) getSpatial();
terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely // 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); return new UpdateLOD(locations, lodCalculator);
} }
@ -246,115 +356,78 @@ public class TerrainLodControl extends AbstractControl {
* Back on the ogl thread: update the terrain patch geometries * Back on the ogl thread: update the terrain patch geometries
*/ */
private void updateQuadLODs() { private void updateQuadLODs() {
if (indexer != null) {
if (indexer.isDone()) { if (indexer == null || !indexer.isDone()) {
try { return;
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;
}
}
} }
}
private boolean lastCameraLocationsTheSame(List<Vector3f> locations) { try {
boolean theSame = true;
for (Vector3f l : locations) { final HashMap<String, UpdatedTerrainPatch> updated = indexer.get();
for (Vector3f v : lastCameraLocations) { if (updated != null) {
if (!v.equals(l) ) { // do the actual geometry update here
theSame = false; for (final UpdatedTerrainPatch utp : updated.values()) {
return false; 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() { private SafeArrayList<Vector3f> cloneVectorList(SafeArrayList<Vector3f> locations) {
return lodCalcRunning.get();
}
protected synchronized void setLodCalcRunning(boolean running) { final SafeArrayList<Vector3f> cloned = new SafeArrayList<>(Vector3f.class, locations.size());
lodCalcRunning.set(running);
} 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; return cloned;
} }
@Override @Override
public Object jmeClone() { public Object jmeClone() {
if (spatial instanceof Terrain) { try {
TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameras); return super.clone();
cloned.setLodCalculator(lodCalculator.clone()); } catch (final CloneNotSupportedException e) {
cloned.spatial = spatial; throw new RuntimeException(e);
return cloned;
} }
return null;
} }
@Override @Override
public void cloneFields( Cloner cloner, Object original ) { public void cloneFields(final Cloner cloner, final Object original) {
super.cloneFields(cloner, original); super.cloneFields(cloner, original);
this.lodCalculator = cloner.clone(lodCalculator); this.lodCalculator = cloner.clone(lodCalculator);
this.cameras = new SafeArrayList<>(Camera.class, cameras);
try { this.cameraLocations = new SafeArrayList<>(Vector3f.class);
// Not deep clone of the cameras themselves this.lastCameraLocations = new SafeArrayList<>(Vector3f.class);
this.cameras = cloner.javaClone(cameras); this.lodCalcRunning = new AtomicBoolean();
} catch( CloneNotSupportedException e ) { this.previousCameraLocation = new Vector3f();
throw new RuntimeException("Error cloning", e);
}
} }
@Override @Override
public Control cloneForSpatial(Spatial spatial) { public Control cloneForSpatial(final Spatial spatial) {
if (spatial instanceof Terrain) { if (spatial instanceof Terrain) {
List<Camera> cameraClone = new ArrayList<Camera>(); TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, new ArrayList<>(cameras));
if (cameras != null) {
for (Camera c : cameras) {
cameraClone.add(c);
}
}
TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameraClone);
cloned.setLodCalculator(lodCalculator.clone()); cloned.setLodCalculator(lodCalculator.clone());
return cloned; return cloned;
} }
return null; return null;
} }
public void setCamera(Camera camera) { public void setCamera(final Camera camera) {
List<Camera> cams = new ArrayList<Camera>(); this.cameras.clear();
cams.add(camera); this.cameras.add(camera);
setCameras(cams);
} }
public void setCameras(List<Camera> cameras) { public void setCameras(final List<Camera> cameras) {
this.cameras = cameras; this.cameras.clear();
cameraLocations.clear(); this.cameras.addAll(cameras);
for (Camera c : cameras) {
cameraLocations.add(c.getLocation());
}
} }
@Override @Override
@ -373,7 +446,7 @@ public class TerrainLodControl extends AbstractControl {
return lodCalculator; return lodCalculator;
} }
public void setLodCalculator(LodCalculator lodCalculator) { public void setLodCalculator(final LodCalculator lodCalculator) {
this.lodCalculator = lodCalculator; this.lodCalculator = lodCalculator;
} }
@ -393,64 +466,60 @@ public class TerrainLodControl extends AbstractControl {
/** /**
* Calculates the LOD of all child terrain patches. * Calculates the LOD of all child terrain patches.
*/ */
protected class UpdateLOD implements Callable<HashMap<String,UpdatedTerrainPatch>> { protected class UpdateLOD implements Callable<HashMap<String, UpdatedTerrainPatch>> {
protected List<Vector3f> camLocations;
protected LodCalculator lodCalculator;
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.camLocations = camLocations;
this.lodCalculator = lodCalculator; this.lodCalculator = lodCalculator;
} }
public HashMap<String, UpdatedTerrainPatch> call() throws Exception { 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 // go through each patch and calculate its LOD based on camera distance
HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>(); HashMap<String, UpdatedTerrainPatch> updated = new HashMap<>();
boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here // 'updated' gets populated here
boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator);
if (!lodChanged) { if (!lodChanged) {
// not worth updating anything else since no one's LOD changed // not worth updating anything else since no one's LOD changed
setLodCalcRunning(false); lodCalcRunning.set(false);
return null; return null;
} }
// then calculate its neighbour LOD values for seaming in the shader // then calculate its neighbour LOD values for seaming in the shader
terrainQuad.findNeighboursLod(updated); terrainQuad.findNeighboursLod(updated);
// 'updated' can get added to here
terrainQuad.fixEdges(updated); // 'updated' can get added to here terrainQuad.fixEdges(updated);
terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod()); terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod());
//setUpdateQuadLODs(updated); // set back to main ogl thread //setUpdateQuadLODs(updated); // set back to main ogl thread
setLodCalcRunning(false); lodCalcRunning.set(false);
return updated; return updated;
} }
} }
@Override @Override
public void write(JmeExporter ex) throws IOException { public void write(final JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write((Node)terrain, "terrain", null); oc.write((Node)terrain, "terrain", null);
oc.write(lodCalculator, "lodCalculator", null); oc.write(lodCalculator, "lodCalculator", null);
oc.write(useRenderCamera, "useRenderCamera", false);
} }
@Override @Override
public void read(JmeImporter im) throws IOException { public void read(final JmeImporter im) throws IOException {
super.read(im); super.read(im);
InputCapsule ic = im.getCapsule(this); InputCapsule ic = im.getCapsule(this);
terrain = (Terrain) ic.readSavable("terrain", null); terrain = (Terrain) ic.readSavable("terrain", null);
lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator()); lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator());
useRenderCamera = ic.readBoolean("useRenderCamera", false);
} }
} }

Loading…
Cancel
Save