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.
This commit is contained in:
parent
6e07a214c6
commit
15fd80d67f
@ -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>();
|
|
||||||
|
|
||||||
public MultiTerrainLodControl(List<Camera> cameras) {
|
private List<TerrainQuad> addedTerrains;
|
||||||
this.cameras = cameras;
|
private List<TerrainQuad> removedTerrains;
|
||||||
lodCalculator = new DistanceLodCalculator(65, 2.7f);
|
|
||||||
|
public MultiTerrainLodControl() {
|
||||||
|
terrains = new SafeArrayList<>(TerrainQuad.class);
|
||||||
|
removedTerrains = new ArrayList<>();
|
||||||
|
addedTerrains = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiTerrainLodControl(Camera camera) {
|
public MultiTerrainLodControl(final Terrain terrain) {
|
||||||
List<Camera> cams = new ArrayList<Camera>();
|
this();
|
||||||
cams.add(camera);
|
setTerrain(terrain);
|
||||||
this.cameras = cams;
|
}
|
||||||
lodCalculator = new DistanceLodCalculator(65, 2.7f);
|
|
||||||
|
public MultiTerrainLodControl(final Camera camera) {
|
||||||
|
this();
|
||||||
|
setCamera(camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiTerrainLodControl(final Terrain terrain, final Camera camera) {
|
||||||
|
this(terrain);
|
||||||
|
setCamera(camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
@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,9 +116,10 @@ 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 SafeArrayList<Camera> cameras;
|
||||||
|
protected SafeArrayList<Vector3f> cameraLocations;
|
||||||
|
protected SafeArrayList<Vector3f> lastCameraLocations;
|
||||||
|
|
||||||
|
protected AtomicBoolean lodCalcRunning;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The previous location of {@link #camera}.
|
||||||
|
*/
|
||||||
|
protected Vector3f previousCameraLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The camera from render view port.
|
||||||
|
*/
|
||||||
|
protected Camera camera;
|
||||||
|
|
||||||
|
protected Terrain terrain;
|
||||||
protected LodCalculator lodCalculator;
|
protected LodCalculator lodCalculator;
|
||||||
private boolean hasResetLod = false; // used when enabled is set to false
|
|
||||||
|
|
||||||
private HashMap<String,UpdatedTerrainPatch> updatedPatches;
|
|
||||||
private final Object updatePatchesLock = new Object();
|
|
||||||
|
|
||||||
protected List<Vector3f> lastCameraLocations; // used for LOD calc
|
|
||||||
private AtomicBoolean lodCalcRunning = new AtomicBoolean(false);
|
|
||||||
private int lodOffCount = 0;
|
|
||||||
|
|
||||||
protected ExecutorService executor;
|
|
||||||
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) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set your own custom executor to be used. The control will use
|
* @param useRenderCamera true if need to use a camera from render view port.
|
||||||
* this instead of creating its own.
|
|
||||||
*/
|
*/
|
||||||
public void setExecutor(ExecutorService executor) {
|
public void setUseRenderCamera(final boolean useRenderCamera) {
|
||||||
this.executor = executor;
|
this.useRenderCamera = useRenderCamera;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ExecutorService createExecutorService() {
|
/**
|
||||||
return Executors.newSingleThreadExecutor(new ThreadFactory() {
|
* @return true if need to use a camera from render view port.
|
||||||
public Thread newThread(Runnable r) {
|
*/
|
||||||
Thread th = new Thread(r);
|
public boolean isUseRenderCamera() {
|
||||||
th.setName("jME3 Terrain Thread");
|
return useRenderCamera;
|
||||||
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
|
@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
|
|
||||||
if (lodOffCount == 1)
|
|
||||||
return;
|
return;
|
||||||
else
|
}
|
||||||
lodOffCount++;
|
|
||||||
} else
|
|
||||||
lodOffCount = 0;
|
|
||||||
|
|
||||||
if (lastCameraLocations != null) {
|
final Vector3f currentLocation = camera.getLocation();
|
||||||
if (!forceUpdate && lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff())
|
|
||||||
|
if (!forceUpdate && previousCameraLocation.equals(currentLocation) && !lodCalculator.isLodOff()) {
|
||||||
return; // don't update if in same spot
|
return; // don't update if in same spot
|
||||||
else
|
} else {
|
||||||
lastCameraLocations = cloneVectorList(locations);
|
previousCameraLocation.set(currentLocation);
|
||||||
|
}
|
||||||
|
|
||||||
forceUpdate = false;
|
forceUpdate = false;
|
||||||
}
|
|
||||||
else {
|
if (!lodCalcRunning.compareAndSet(false, true)) {
|
||||||
lastCameraLocations = cloneVectorList(locations);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLodCalcRunning()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLodCalcRunning(true);
|
|
||||||
|
|
||||||
if (executor == null)
|
|
||||||
executor = createExecutorService();
|
|
||||||
|
|
||||||
prepareTerrain();
|
prepareTerrain();
|
||||||
|
|
||||||
UpdateLOD updateLodThread = getLodThread(locations, lodCalculator);
|
final SafeArrayList<Vector3f> locations = new SafeArrayList<>(Vector3f.class, 1);
|
||||||
indexer = executor.submit(updateLodThread);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update any existing ones that need updating
|
||||||
|
updateQuadLODs();
|
||||||
|
|
||||||
|
if (updateLodOffCount(lodCalculator)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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() {
|
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()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
HashMap<String, UpdatedTerrainPatch> updated = indexer.get();
|
final HashMap<String, UpdatedTerrainPatch> updated = indexer.get();
|
||||||
if (updated != null) {
|
if (updated != null) {
|
||||||
// do the actual geometry update here
|
// do the actual geometry update here
|
||||||
for (UpdatedTerrainPatch utp : updated.values()) {
|
for (final UpdatedTerrainPatch utp : updated.values()) {
|
||||||
utp.updateAll();
|
utp.updateAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (InterruptedException ex) {
|
} catch (final InterruptedException | ExecutionException ex) {
|
||||||
Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
} catch (ExecutionException ex) {
|
|
||||||
Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
} finally {
|
} finally {
|
||||||
indexer = null;
|
indexer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private SafeArrayList<Vector3f> cloneVectorList(SafeArrayList<Vector3f> locations) {
|
||||||
|
|
||||||
|
final SafeArrayList<Vector3f> cloned = new SafeArrayList<>(Vector3f.class, locations.size());
|
||||||
|
|
||||||
|
for (final Vector3f location : locations.getArray()) {
|
||||||
|
cloned.add(location.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return theSame;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected synchronized boolean isLodCalcRunning() {
|
|
||||||
return lodCalcRunning.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected synchronized void setLodCalcRunning(boolean running) {
|
|
||||||
lodCalcRunning.set(running);
|
|
||||||
}
|
|
||||||
|
|
||||||
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…
x
Reference in New Issue
Block a user