Softened terrain API to allow for different tiling implementations. Added MultiTerrainLodControl

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9434 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
bre..ns 13 years ago
parent b5aa49bc16
commit 33a69d4536
  1. 11
      engine/src/terrain/com/jme3/terrain/geomipmap/LODGeomap.java
  2. 127
      engine/src/terrain/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java
  3. 14
      engine/src/terrain/com/jme3/terrain/geomipmap/NeighbourFinder.java
  4. 100
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java
  5. 4
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java
  6. 154
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java
  7. 35
      engine/src/terrain/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java
  8. 12
      engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java
  9. 8
      engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java
  10. 73
      engine/src/test/jme3test/terrain/TerrainTestTile.java

@ -154,6 +154,9 @@ public class LODGeomap extends GeoMap {
*/ */
public IntBuffer writeIndexArrayLodDiff(IntBuffer store, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) { public IntBuffer writeIndexArrayLodDiff(IntBuffer store, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) {
//if (true)
//return writeIndexArrayLodVariable(store, lod, height, lod, lod, lod);
IntBuffer buffer2 = store; IntBuffer buffer2 = store;
int numIndexes = calculateNumIndexesLodDiff(lod); int numIndexes = calculateNumIndexesLodDiff(lod);
if (store == null) { if (store == null) {
@ -275,7 +278,7 @@ public class LODGeomap extends GeoMap {
buffer.put(idx); buffer.put(idx);
idx = (row + 2 * lod) * getWidth(); idx = (row + 2 * lod) * getWidth();
buffer.put(idx); buffer.put(idx);
if (row < getWidth() - lod - 2 - 1) { //if not the last one if (row < getWidth() - 1 - 2 * lod) { //if not the last one
idx = (row + 2 * lod) * getWidth() + lod; idx = (row + 2 * lod) * getWidth() + lod;
buffer.put(idx); buffer.put(idx);
idx = (row + 2 * lod) * getWidth(); idx = (row + 2 * lod) * getWidth();
@ -917,10 +920,12 @@ public class LODGeomap extends GeoMap {
public void put(int value) { public void put(int value) {
try { try {
delegate.put(value);
count++; count++;
if (count > delegate.limit())
throw new BufferOverflowException();
delegate.put(value);
} catch (BufferOverflowException e) { } catch (BufferOverflowException e) {
//System.out.println("err buffer size: "+delegate.capacity()); System.out.println("err buffer size: "+delegate.capacity());
} }
} }

@ -0,0 +1,127 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.terrain.geomipmap;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* An extension of the TerrainLodControl that handles
* multiple terrains at once. This is to be used if you
* have your own tiling/paging terrain system, such as
* TerrainGrid.
*
* @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);
}
public MultiTerrainLodControl(Camera camera) {
List<Camera> cams = new ArrayList<Camera>();
cams.add(camera);
this.cameras = cams;
lodCalculator = new DistanceLodCalculator(65, 2.7f);
}
/**
* Add a terrain that will have its LOD handled by this control.
* It will be added next update run. You should only call this from
* the render thread.
*/
public void addTerrain(TerrainQuad tq) {
addedTerrains.add(tq);
}
/**
* Add a terrain that will no longer have its LOD handled by this control.
* It will be removed next update run. You should only call this from
* the render thread.
*/
public void removeTerrain(TerrainQuad tq) {
removedTerrains.remove(tq);
}
@Override
protected UpdateLOD getLodThread(List<Vector3f> locations, LodCalculator lodCalculator) {
return new UpdateMultiLOD(locations, lodCalculator);
}
@Override
protected void prepareTerrain() {
if (!addedTerrains.isEmpty()) {
for (TerrainQuad t : addedTerrains) {
if (!terrains.contains(t))
terrains.add(t);
}
addedTerrains.clear();
}
if (!removedTerrains.isEmpty()) {
terrains.removeAll(removedTerrains);
removedTerrains.clear();
}
for (TerrainQuad terrain : terrains)
terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely
}
/**
* Overrides the parent UpdateLOD runnable to process
* multiple terrains.
*/
protected class UpdateMultiLOD extends UpdateLOD {
protected UpdateMultiLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {
super(camLocations, lodCalculator);
}
@Override
public HashMap<String, UpdatedTerrainPatch> call() throws Exception {
setLodCalcRunning(true);
HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();
for (TerrainQuad terrainQuad : terrains) {
// go through each patch and calculate its LOD based on camera distance
terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here
}
for (TerrainQuad terrainQuad : terrains) {
// then calculate the neighbour LOD values for seaming
terrainQuad.findNeighboursLod(updated);
}
for (TerrainQuad terrainQuad : terrains) {
// check neighbour quads that need their edges seamed
terrainQuad.fixEdges(updated);
}
for (TerrainQuad terrainQuad : terrains) {
// perform the edge seaming, if it requires it
terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod());
}
//setUpdateQuadLODs(updated); // set back to main ogl thread
setLodCalcRunning(false);
return updated;
}
}
}

@ -13,7 +13,7 @@ package com.jme3.terrain.geomipmap;
* With this you can have a parent, control or spatial, that manages a group of * With this you can have a parent, control or spatial, that manages a group of
* TerrainQuads by linking them together through these four methods. * TerrainQuads by linking them together through these four methods.
* *
* The general orientation of TerrainQuads and their sun-quads is as such: * The general orientation of TerrainQuads and their sub-quads is as such:
* *
* *
* +-- x+ ----> * +-- x+ ---->
@ -32,11 +32,23 @@ package com.jme3.terrain.geomipmap;
*/ */
public interface NeighbourFinder { public interface NeighbourFinder {
/**
* Get the TerrainQuad to the right of the supplied 'center' quad.
*/
public TerrainQuad getRightQuad(TerrainQuad center); public TerrainQuad getRightQuad(TerrainQuad center);
/**
* Get the TerrainQuad to the left of the supplied 'center' quad.
*/
public TerrainQuad getLeftQuad(TerrainQuad center); public TerrainQuad getLeftQuad(TerrainQuad center);
/**
* Get the TerrainQuad above the supplied 'center' quad.
*/
public TerrainQuad getTopQuad(TerrainQuad center); public TerrainQuad getTopQuad(TerrainQuad center);
/**
* Get the TerrainQuad below the supplied 'center' quad.
*/
public TerrainQuad getDownQuad(TerrainQuad center); public TerrainQuad getDownQuad(TerrainQuad center);
} }

@ -50,9 +50,15 @@ 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.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Tells the terrain to update its Level of Detail. * Tells the terrain to update its Level of Detail.
@ -72,19 +78,20 @@ import java.util.concurrent.ThreadFactory;
public class TerrainLodControl extends AbstractControl { public class TerrainLodControl extends AbstractControl {
private Terrain terrain; private Terrain terrain;
private List<Camera> cameras; protected List<Camera> cameras;
private List<Vector3f> cameraLocations = new ArrayList<Vector3f>(); private List<Vector3f> cameraLocations = new ArrayList<Vector3f>();
private LodCalculator lodCalculator; protected LodCalculator lodCalculator;
private boolean hasResetLod = false; // used when enabled is set to false private boolean hasResetLod = false; // used when enabled is set to false
private HashMap<String,UpdatedTerrainPatch> updatedPatches; private HashMap<String,UpdatedTerrainPatch> updatedPatches;
private final Object updatePatchesLock = new Object(); private final Object updatePatchesLock = new Object();
protected List<Vector3f> lastCameraLocations; // used for LOD calc protected List<Vector3f> lastCameraLocations; // used for LOD calc
private boolean lodCalcRunning = false; private AtomicBoolean lodCalcRunning = new AtomicBoolean(false);
private int lodOffCount = 0; private int lodOffCount = 0;
protected ExecutorService executor; protected ExecutorService executor;
protected Future<HashMap<String, UpdatedTerrainPatch>> indexer;
public TerrainLodControl() { public TerrainLodControl() {
} }
@ -177,6 +184,7 @@ public class TerrainLodControl extends AbstractControl {
if (isLodCalcRunning()) { if (isLodCalcRunning()) {
return; return;
} }
setLodCalcRunning(true);
//if (getParent() instanceof TerrainQuad) { //if (getParent() instanceof TerrainQuad) {
// return; // we just want the root quad to perform this. // return; // we just want the root quad to perform this.
@ -185,25 +193,47 @@ public class TerrainLodControl extends AbstractControl {
if (executor == null) if (executor == null)
executor = createExecutorService(); executor = createExecutorService();
TerrainQuad terrain = (TerrainQuad)getSpatial(); prepareTerrain();
terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely
UpdateLOD updateLodThread = new UpdateLOD(locations, lodCalculator); UpdateLOD updateLodThread = getLodThread(locations, lodCalculator);
executor.execute(updateLodThread); indexer = executor.submit(updateLodThread);
} }
private void setUpdateQuadLODs(HashMap<String,UpdatedTerrainPatch> updated) { protected void prepareTerrain() {
synchronized (updatePatchesLock) { TerrainQuad terrain = (TerrainQuad)getSpatial();
updatedPatches = updated; terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely
} }
protected UpdateLOD getLodThread(List<Vector3f> locations, LodCalculator lodCalculator) {
return new UpdateLOD(locations, lodCalculator);
} }
/** /**
* Back on the ogl thread: update the terrain patch geometries * Back on the ogl thread: update the terrain patch geometries
* @param updatedPatches to be updated
*/ */
private void updateQuadLODs() { private void updateQuadLODs() {
synchronized (updatePatchesLock) { 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;
}
}
}
/*synchronized (updatePatchesLock) {
if (updatedPatches == null || updatedPatches.isEmpty()) if (updatedPatches == null || updatedPatches.isEmpty())
return; return;
@ -213,13 +243,13 @@ public class TerrainLodControl extends AbstractControl {
utp.updateAll(); utp.updateAll();
} }
updatedPatches.clear(); updatedPatches = null;
} }*/
} }
public boolean hasPatchesToUpdate() { //public boolean hasPatchesToUpdate() {
return updatedPatches != null && !updatedPatches.isEmpty(); // return updatedPatches != null && !updatedPatches.isEmpty();
} //}
private boolean lastCameraLocationsTheSame(List<Vector3f> locations) { private boolean lastCameraLocationsTheSame(List<Vector3f> locations) {
boolean theSame = true; boolean theSame = true;
@ -234,12 +264,12 @@ public class TerrainLodControl extends AbstractControl {
return theSame; return theSame;
} }
private synchronized boolean isLodCalcRunning() { protected synchronized boolean isLodCalcRunning() {
return lodCalcRunning; return lodCalcRunning.get();
} }
private synchronized void setLodCalcRunning(boolean running) { protected synchronized void setLodCalcRunning(boolean running) {
lodCalcRunning = running; lodCalcRunning.set(running);
} }
private List<Vector3f> cloneVectorList(List<Vector3f> locations) { private List<Vector3f> cloneVectorList(List<Vector3f> locations) {
@ -320,22 +350,20 @@ public class TerrainLodControl extends AbstractControl {
/** /**
* Calculates the LOD of all child terrain patches. * Calculates the LOD of all child terrain patches.
*/ */
private class UpdateLOD implements Runnable { protected class UpdateLOD implements Callable<HashMap<String,UpdatedTerrainPatch>> {
private List<Vector3f> camLocations; protected List<Vector3f> camLocations;
private LodCalculator lodCalculator; protected LodCalculator lodCalculator;
UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) { protected UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {
this.camLocations = camLocations; this.camLocations = camLocations;
this.lodCalculator = lodCalculator; this.lodCalculator = lodCalculator;
} }
public void run() { public HashMap<String, UpdatedTerrainPatch> call() throws Exception {
long start = System.currentTimeMillis(); //long start = System.currentTimeMillis();
if (isLodCalcRunning()) { //if (isLodCalcRunning()) {
//System.out.println("thread already running"); // return null;
return; //}
}
//System.out.println("spawned thread "+toString());
setLodCalcRunning(true); setLodCalcRunning(true);
TerrainQuad terrainQuad = (TerrainQuad)getSpatial(); TerrainQuad terrainQuad = (TerrainQuad)getSpatial();
@ -347,7 +375,7 @@ public class TerrainLodControl extends AbstractControl {
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); setLodCalcRunning(false);
return; return null;
} }
@ -358,11 +386,11 @@ public class TerrainLodControl extends AbstractControl {
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); setLodCalcRunning(false);
//double duration = (System.currentTimeMillis()-start);
//System.out.println("terminated in "+duration); return updated;
} }
} }

@ -80,7 +80,7 @@ import java.util.List;
public class TerrainPatch extends Geometry { public class TerrainPatch extends Geometry {
protected LODGeomap geomap; protected LODGeomap geomap;
protected int lod = -1; // this terrain patch's LOD protected int lod = 0; // this terrain patch's LOD
private int maxLod = -1; private int maxLod = -1;
protected int previousLod = -1; protected int previousLod = -1;
protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODs protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODs
@ -234,7 +234,7 @@ public class TerrainPatch extends Geometry {
UpdatedTerrainPatch utp = updated.get(getName()); UpdatedTerrainPatch utp = updated.get(getName());
if (utp != null && (utp.isReIndexNeeded() || utp.isFixEdges()) ) { if (utp != null && utp.isReIndexNeeded() ) {
int pow = (int) Math.pow(2, utp.getNewLod()); int pow = (int) Math.pow(2, utp.getNewLod());
boolean left = utp.getLeftLod() > utp.getNewLod(); boolean left = utp.getLeftLod() > utp.getNewLod();
boolean top = utp.getTopLod() > utp.getNewLod(); boolean top = utp.getTopLod() > utp.getNewLod();

@ -201,6 +201,7 @@ public class TerrainQuad extends Node implements Terrain {
public void setNeighbourFinder(NeighbourFinder neighbourFinder) { public void setNeighbourFinder(NeighbourFinder neighbourFinder) {
this.neighbourFinder = neighbourFinder; this.neighbourFinder = neighbourFinder;
resetCachedNeighbours();
} }
/** /**
@ -360,6 +361,8 @@ public class TerrainQuad extends Node implements Terrain {
} }
TerrainPatch right = patch.rightNeighbour; TerrainPatch right = patch.rightNeighbour;
TerrainPatch down = patch.bottomNeighbour; TerrainPatch down = patch.bottomNeighbour;
TerrainPatch left = patch.leftNeighbour;
TerrainPatch top = patch.topNeighbour;
UpdatedTerrainPatch utp = updated.get(patch.getName()); UpdatedTerrainPatch utp = updated.get(patch.getName());
if (utp == null) { if (utp == null) {
@ -370,34 +373,55 @@ public class TerrainQuad extends Node implements Terrain {
if (right != null) { if (right != null) {
UpdatedTerrainPatch utpR = updated.get(right.getName()); UpdatedTerrainPatch utpR = updated.get(right.getName());
if (utpR == null) { if (utpR == null) {
utpR = new UpdatedTerrainPatch(right, right.lod); utpR = new UpdatedTerrainPatch(right);
updated.put(utpR.getName(), utpR); updated.put(utpR.getName(), utpR);
utpR.setNewLod(right.lod);
} }
utp.setRightLod(utpR.getNewLod()); utp.setRightLod(utpR.getNewLod());
utpR.setLeftLod(utp.getNewLod()); utpR.setLeftLod(utp.getNewLod());
} }
if (down != null) { if (down != null) {
UpdatedTerrainPatch utpD = updated.get(down.getName()); UpdatedTerrainPatch utpD = updated.get(down.getName());
if (utpD == null) { if (utpD == null) {
utpD = new UpdatedTerrainPatch(down, down.lod); utpD = new UpdatedTerrainPatch(down);
updated.put(utpD.getName(), utpD); updated.put(utpD.getName(), utpD);
utpD.setNewLod(down.lod);
} }
utp.setBottomLod(utpD.getNewLod()); utp.setBottomLod(utpD.getNewLod());
utpD.setTopLod(utp.getNewLod()); utpD.setTopLod(utp.getNewLod());
} }
if (left != null) {
UpdatedTerrainPatch utpL = updated.get(left.getName());
if (utpL == null) {
utpL = new UpdatedTerrainPatch(left);
updated.put(utpL.getName(), utpL);
utpL.setNewLod(left.lod);
}
utp.setLeftLod(utpL.getNewLod());
utpL.setRightLod(utp.getNewLod());
}
if (top != null) {
UpdatedTerrainPatch utpT = updated.get(top.getName());
if (utpT == null) {
utpT = new UpdatedTerrainPatch(top);
updated.put(utpT.getName(), utpT);
utpT.setNewLod(top.lod);
}
utp.setTopLod(utpT.getNewLod());
utpT.setBottomLod(utp.getNewLod());
}
} }
} }
} }
} }
/** /**
* Reset the cached references of neighbours.
* TerrainQuad caches neighbours for faster LOD checks. * TerrainQuad caches neighbours for faster LOD checks.
* Sometimes you might want to reset this cache (for instance in TerrainGrid) * Sometimes you might want to reset this cache (for instance in TerrainGrid)
*/ */
protected void resetCachedNeighbours() { public void resetCachedNeighbours() {
if (children != null) { if (children != null) {
for (int x = children.size(); --x >= 0;) { for (int x = children.size(); --x >= 0;) {
Spatial child = children.get(x); Spatial child = children.get(x);
@ -441,33 +465,41 @@ public class TerrainQuad extends Node implements Terrain {
if (right != null) { if (right != null) {
UpdatedTerrainPatch utpR = updated.get(right.getName()); UpdatedTerrainPatch utpR = updated.get(right.getName());
if (utpR == null) { if (utpR == null) {
utpR = new UpdatedTerrainPatch(right, right.lod); utpR = new UpdatedTerrainPatch(right);
updated.put(utpR.getName(), utpR); updated.put(utpR.getName(), utpR);
utpR.setNewLod(right.lod);
} }
utpR.setLeftLod(utp.getNewLod());
utpR.setFixEdges(true); utpR.setFixEdges(true);
} }
if (down != null) { if (down != null) {
UpdatedTerrainPatch utpD = updated.get(down.getName()); UpdatedTerrainPatch utpD = updated.get(down.getName());
if (utpD == null) { if (utpD == null) {
utpD = new UpdatedTerrainPatch(down, down.lod); utpD = new UpdatedTerrainPatch(down);
updated.put(utpD.getName(), utpD); updated.put(utpD.getName(), utpD);
utpD.setNewLod(down.lod);
} }
utpD.setTopLod(utp.getNewLod());
utpD.setFixEdges(true); utpD.setFixEdges(true);
} }
if (top != null){ if (top != null){
UpdatedTerrainPatch utpT = updated.get(top.getName()); UpdatedTerrainPatch utpT = updated.get(top.getName());
if (utpT == null) { if (utpT == null) {
utpT = new UpdatedTerrainPatch(top, top.lod); utpT = new UpdatedTerrainPatch(top);
updated.put(utpT.getName(), utpT); updated.put(utpT.getName(), utpT);
utpT.setNewLod(top.lod);
} }
utpT.setBottomLod(utp.getNewLod());
utpT.setFixEdges(true); utpT.setFixEdges(true);
} }
if (left != null){ if (left != null){
UpdatedTerrainPatch utpL = updated.get(left.getName()); UpdatedTerrainPatch utpL = updated.get(left.getName());
if (utpL == null) { if (utpL == null) {
utpL = new UpdatedTerrainPatch(left, left.lod); utpL = new UpdatedTerrainPatch(left);
updated.put(utpL.getName(), utpL); updated.put(utpL.getName(), utpL);
utpL.setNewLod(left.lod);
} }
utpL.setRightLod(utp.getNewLod());
utpL.setFixEdges(true); utpL.setFixEdges(true);
} }
} }
@ -1278,6 +1310,8 @@ public class TerrainQuad extends Node implements Terrain {
} }
protected TerrainQuad getQuad(int quad) { protected TerrainQuad getQuad(int quad) {
if (quad == 0)
return this;
if (children != null) if (children != null)
for (int x = children.size(); --x >= 0;) { for (int x = children.size(); --x >= 0;) {
Spatial child = children.get(x); Spatial child = children.get(x);
@ -1355,7 +1389,7 @@ public class TerrainQuad extends Node implements Terrain {
else if (tp.getQuadrant() == 4) else if (tp.getQuadrant() == 4)
return getPatch(2); return getPatch(2);
else if (tp.getQuadrant() == 1) { else if (tp.getQuadrant() == 1) {
// find the patch above and ask it for child 2. // find the patch above and ask it for child 3.
TerrainQuad quad = findLeftQuad(); TerrainQuad quad = findLeftQuad();
if (quad != null) if (quad != null)
return quad.getPatch(3); return quad.getPatch(3);
@ -1370,34 +1404,35 @@ public class TerrainQuad extends Node implements Terrain {
protected TerrainQuad findRightQuad() { protected TerrainQuad findRightQuad() {
boolean useFinder = false; boolean useFinder = false;
if (getParent() == null || !(getParent() instanceof TerrainQuad)) if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
if (neighbourFinder == null) if (neighbourFinder == null)
return null; return null;
else else
useFinder = true; useFinder = true;
}
TerrainQuad pQuad = (TerrainQuad) getParent(); TerrainQuad pQuad = null;
if (!useFinder)
pQuad = (TerrainQuad) getParent();
if (quadrant == 1) if (quadrant == 1)
return pQuad.getQuad(3); return pQuad.getQuad(3);
else if (quadrant == 2) else if (quadrant == 2)
return pQuad.getQuad(4); return pQuad.getQuad(4);
else if (quadrant == 3) { else if (quadrant == 3) {
TerrainQuad quad = null; TerrainQuad quad = pQuad.findRightQuad();
if (useFinder)
quad = neighbourFinder.getRightQuad(this);
else
quad = pQuad.findRightQuad();
if (quad != null) if (quad != null)
return quad.getQuad(1); return quad.getQuad(1);
} else if (quadrant == 4) { } else if (quadrant == 4) {
TerrainQuad quad = null; TerrainQuad quad = pQuad.findRightQuad();
if (useFinder)
quad = neighbourFinder.getRightQuad(this);
else
quad = pQuad.findRightQuad();
if (quad != null) if (quad != null)
return quad.getQuad(2); return quad.getQuad(2);
} else if (quadrant == 0) {
// at the top quad
if (useFinder) {
TerrainQuad quad = neighbourFinder.getRightQuad(this);
return quad;
}
} }
return null; return null;
@ -1405,34 +1440,35 @@ public class TerrainQuad extends Node implements Terrain {
protected TerrainQuad findDownQuad() { protected TerrainQuad findDownQuad() {
boolean useFinder = false; boolean useFinder = false;
if (getParent() == null || !(getParent() instanceof TerrainQuad)) if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
if (neighbourFinder == null) if (neighbourFinder == null)
return null; return null;
else else
useFinder = true; useFinder = true;
}
TerrainQuad pQuad = (TerrainQuad) getParent(); TerrainQuad pQuad = null;
if (!useFinder)
pQuad = (TerrainQuad) getParent();
if (quadrant == 1) if (quadrant == 1)
return pQuad.getQuad(2); return pQuad.getQuad(2);
else if (quadrant == 3) else if (quadrant == 3)
return pQuad.getQuad(4); return pQuad.getQuad(4);
else if (quadrant == 2) { else if (quadrant == 2) {
TerrainQuad quad = null; TerrainQuad quad = pQuad.findDownQuad();
if (useFinder)
quad = neighbourFinder.getDownQuad(this);
else
quad = pQuad.findDownQuad();
if (quad != null) if (quad != null)
return quad.getQuad(1); return quad.getQuad(1);
} else if (quadrant == 4) { } else if (quadrant == 4) {
TerrainQuad quad = null; TerrainQuad quad = pQuad.findDownQuad();
if (useFinder)
quad = neighbourFinder.getDownQuad(this);
else
quad = pQuad.findDownQuad();
if (quad != null) if (quad != null)
return quad.getQuad(3); return quad.getQuad(3);
} else if (quadrant == 0) {
// at the top quad
if (useFinder) {
TerrainQuad quad = neighbourFinder.getDownQuad(this);
return quad;
}
} }
return null; return null;
@ -1440,34 +1476,35 @@ public class TerrainQuad extends Node implements Terrain {
protected TerrainQuad findTopQuad() { protected TerrainQuad findTopQuad() {
boolean useFinder = false; boolean useFinder = false;
if (getParent() == null || !(getParent() instanceof TerrainQuad)) if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
if (neighbourFinder == null) if (neighbourFinder == null)
return null; return null;
else else
useFinder = true; useFinder = true;
}
TerrainQuad pQuad = (TerrainQuad) getParent(); TerrainQuad pQuad = null;
if (!useFinder)
pQuad = (TerrainQuad) getParent();
if (quadrant == 2) if (quadrant == 2)
return pQuad.getQuad(1); return pQuad.getQuad(1);
else if (quadrant == 4) else if (quadrant == 4)
return pQuad.getQuad(3); return pQuad.getQuad(3);
else if (quadrant == 1) { else if (quadrant == 1) {
TerrainQuad quad = null; TerrainQuad quad = pQuad.findTopQuad();
if (useFinder)
quad = neighbourFinder.getTopQuad(this);
else
quad = pQuad.findTopQuad();
if (quad != null) if (quad != null)
return quad.getQuad(2); return quad.getQuad(2);
} else if (quadrant == 3) { } else if (quadrant == 3) {
TerrainQuad quad = null; TerrainQuad quad = pQuad.findTopQuad();
if (useFinder)
quad = neighbourFinder.getTopQuad(this);
else
quad = pQuad.findTopQuad();
if (quad != null) if (quad != null)
return quad.getQuad(4); return quad.getQuad(4);
} else if (quadrant == 0) {
// at the top quad
if (useFinder) {
TerrainQuad quad = neighbourFinder.getTopQuad(this);
return quad;
}
} }
return null; return null;
@ -1475,34 +1512,35 @@ public class TerrainQuad extends Node implements Terrain {
protected TerrainQuad findLeftQuad() { protected TerrainQuad findLeftQuad() {
boolean useFinder = false; boolean useFinder = false;
if (getParent() == null || !(getParent() instanceof TerrainQuad)) if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
if (neighbourFinder == null) if (neighbourFinder == null)
return null; return null;
else else
useFinder = true; useFinder = true;
}
TerrainQuad pQuad = (TerrainQuad) getParent(); TerrainQuad pQuad = null;
if (!useFinder)
pQuad = (TerrainQuad) getParent();
if (quadrant == 3) if (quadrant == 3)
return pQuad.getQuad(1); return pQuad.getQuad(1);
else if (quadrant == 4) else if (quadrant == 4)
return pQuad.getQuad(2); return pQuad.getQuad(2);
else if (quadrant == 1) { else if (quadrant == 1) {
TerrainQuad quad = null; TerrainQuad quad = pQuad.findLeftQuad();
if (useFinder)
quad = neighbourFinder.getLeftQuad(this);
else
quad = pQuad.findLeftQuad();
if (quad != null) if (quad != null)
return quad.getQuad(3); return quad.getQuad(3);
} else if (quadrant == 2) { } else if (quadrant == 2) {
TerrainQuad quad = null; TerrainQuad quad = pQuad.findLeftQuad();
if (useFinder)
quad = neighbourFinder.getLeftQuad(this);
else
quad = pQuad.findLeftQuad();
if (quad != null) if (quad != null)
return quad.getQuad(4); return quad.getQuad(4);
} else if (quadrant == 0) {
// at the top quad
if (useFinder) {
TerrainQuad quad = neighbourFinder.getLeftQuad(this);
return quad;
}
} }
return null; return null;

@ -49,21 +49,16 @@ public class UpdatedTerrainPatch {
private int previousLod; private int previousLod;
private int rightLod,topLod,leftLod,bottomLod; private int rightLod,topLod,leftLod,bottomLod;
private IntBuffer newIndexBuffer; private IntBuffer newIndexBuffer;
private boolean reIndexNeeded = false; //private boolean reIndexNeeded = false;
private boolean fixEdges = false; private boolean fixEdges = false;
public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod) { public UpdatedTerrainPatch(TerrainPatch updatedPatch) {
this.updatedPatch = updatedPatch; this.updatedPatch = updatedPatch;
this.newLod = newLod;
} }
public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod, int prevLOD, boolean reIndexNeeded) { public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod) {
this.updatedPatch = updatedPatch; this.updatedPatch = updatedPatch;
this.newLod = newLod; this.newLod = newLod;
this.previousLod = prevLOD;
this.reIndexNeeded = reIndexNeeded;
if (this.newLod <= 0)
throw new IllegalArgumentException();
} }
public String getName() { public String getName() {
@ -71,7 +66,7 @@ public class UpdatedTerrainPatch {
} }
protected boolean lodChanged() { protected boolean lodChanged() {
if (reIndexNeeded && previousLod != newLod) if ( previousLod != newLod)
return true; return true;
else else
return false; return false;
@ -92,12 +87,12 @@ public class UpdatedTerrainPatch {
public void setNewLod(int newLod) { public void setNewLod(int newLod) {
this.newLod = newLod; this.newLod = newLod;
if (this.newLod < 0) if (this.newLod < 0)
throw new IllegalArgumentException(); throw new IllegalArgumentException("newLod cannot be less than zero, was: "+newLod);
} }
protected IntBuffer getNewIndexBuffer() { /*protected IntBuffer getNewIndexBuffer() {
return newIndexBuffer; return newIndexBuffer;
} }*/
protected void setNewIndexBuffer(IntBuffer newIndexBuffer) { protected void setNewIndexBuffer(IntBuffer newIndexBuffer) {
this.newIndexBuffer = newIndexBuffer; this.newIndexBuffer = newIndexBuffer;
@ -144,12 +139,16 @@ public class UpdatedTerrainPatch {
} }
public boolean isReIndexNeeded() { public boolean isReIndexNeeded() {
return reIndexNeeded; if (lodChanged() || isFixEdges())
return true;
//if (leftLod != newLod || rightLod != newLod || bottomLod != newLod || topLod != newLod)
// return true;
return false;
} }
public void setReIndexNeeded(boolean reIndexNeeded) { /*public void setReIndexNeeded(boolean reIndexNeeded) {
this.reIndexNeeded = reIndexNeeded; this.reIndexNeeded = reIndexNeeded;
} }*/
public boolean isFixEdges() { public boolean isFixEdges() {
return fixEdges; return fixEdges;
@ -159,9 +158,9 @@ public class UpdatedTerrainPatch {
this.fixEdges = fixEdges; this.fixEdges = fixEdges;
} }
public int getPreviousLod() { /*public int getPreviousLod() {
return previousLod; return previousLod;
} }*/
public void setPreviousLod(int previousLod) { public void setPreviousLod(int previousLod) {
this.previousLod = previousLod; this.previousLod = previousLod;
@ -173,7 +172,7 @@ public class UpdatedTerrainPatch {
updatedPatch.setLodTop(topLod); updatedPatch.setLodTop(topLod);
updatedPatch.setLodLeft(leftLod); updatedPatch.setLodLeft(leftLod);
updatedPatch.setLodBottom(bottomLod); updatedPatch.setLodBottom(bottomLod);
if (newIndexBuffer != null && (reIndexNeeded || fixEdges)) { if (newIndexBuffer != null && isReIndexNeeded()) {
updatedPatch.setPreviousLod(previousLod); updatedPatch.setPreviousLod(previousLod);
updatedPatch.getMesh().clearBuffer(Type.Index); updatedPatch.getMesh().clearBuffer(Type.Index);
updatedPatch.getMesh().setBuffer(Type.Index, 3, newIndexBuffer); updatedPatch.getMesh().setBuffer(Type.Index, 3, newIndexBuffer);

@ -71,12 +71,12 @@ public class DistanceLodCalculator implements LodCalculator {
int prevLOD = terrainPatch.getLod(); int prevLOD = terrainPatch.getLod();
UpdatedTerrainPatch utp = updates.get(terrainPatch.getName()); UpdatedTerrainPatch utp = updates.get(terrainPatch.getName());
if (utp == null) { if (utp == null) {
utp = new UpdatedTerrainPatch(terrainPatch, 0); utp = new UpdatedTerrainPatch(terrainPatch);
updates.put(utp.getName(), utp); updates.put(utp.getName(), utp);
} }
utp.setNewLod(0); utp.setNewLod(0);
utp.setPreviousLod(prevLOD); utp.setPreviousLod(prevLOD);
utp.setReIndexNeeded(true); //utp.setReIndexNeeded(true);
return true; return true;
} }
@ -89,15 +89,15 @@ public class DistanceLodCalculator implements LodCalculator {
//System.out.println("lod change: "+lod+" > "+i+" dist: "+distance); //System.out.println("lod change: "+lod+" > "+i+" dist: "+distance);
} }
int prevLOD = terrainPatch.getLod(); int prevLOD = terrainPatch.getLod();
//previousLod = lod;
//lod = i;
UpdatedTerrainPatch utp = updates.get(terrainPatch.getName()); UpdatedTerrainPatch utp = updates.get(terrainPatch.getName());
if (utp == null) { if (utp == null) {
utp = new UpdatedTerrainPatch(terrainPatch, i);//save in here, do not update actual variables utp = new UpdatedTerrainPatch(terrainPatch);//save in here, do not update actual variables
updates.put(utp.getName(), utp); updates.put(utp.getName(), utp);
} }
utp.setNewLod(i);
utp.setPreviousLod(prevLOD); utp.setPreviousLod(prevLOD);
utp.setReIndexNeeded(reIndexNeeded); //utp.setReIndexNeeded(reIndexNeeded);
return reIndexNeeded; return reIndexNeeded;
} }

@ -106,15 +106,15 @@ public class PerspectiveLodCalculator implements LodCalculator {
} }
int prevLOD = patch.getLod(); int prevLOD = patch.getLod();
//previousLod = lod;
//lod = i;
UpdatedTerrainPatch utp = updates.get(patch.getName()); UpdatedTerrainPatch utp = updates.get(patch.getName());
if (utp == null) { if (utp == null) {
utp = new UpdatedTerrainPatch(patch, i);//save in here, do not update actual variables utp = new UpdatedTerrainPatch(patch);//save in here, do not update actual variables
updates.put(utp.getName(), utp); updates.put(utp.getName(), utp);
} }
utp.setNewLod(i);
utp.setPreviousLod(prevLOD); utp.setPreviousLod(prevLOD);
utp.setReIndexNeeded(reIndexNeeded); //utp.setReIndexNeeded(reIndexNeeded);
return reIndexNeeded; return reIndexNeeded;
} }
} }

@ -15,11 +15,13 @@ import com.jme3.material.Material;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.shape.Sphere;
import com.jme3.terrain.ProgressMonitor; import com.jme3.terrain.ProgressMonitor;
import com.jme3.terrain.Terrain; import com.jme3.terrain.Terrain;
import com.jme3.terrain.geomipmap.MultiTerrainLodControl;
import com.jme3.terrain.geomipmap.NeighbourFinder; import com.jme3.terrain.geomipmap.NeighbourFinder;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad; import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
@ -29,7 +31,8 @@ import java.util.List;
/** /**
* Demonstrates the NeighbourFinder interface for TerrainQuads, * Demonstrates the NeighbourFinder interface for TerrainQuads,
* allowing you to tile terrains together without having to use * allowing you to tile terrains together without having to use
* TerrainGrid. * TerrainGrid. It also introduces the MultiTerrainLodControl that
* will seam the edges of all the terrains supplied.
* *
* @author sploreg * @author sploreg
*/ */
@ -75,7 +78,31 @@ public class TerrainTestTile extends SimpleApplication {
rootNode.addLight(ambLight); rootNode.addLight(ambLight);
cam.setLocation(new Vector3f(0, 256, 0)); cam.setLocation(new Vector3f(0, 256, 0));
cam.lookAtDirection(new Vector3f(0, -1f, 0).normalizeLocal(), Vector3f.UNIT_X); cam.lookAtDirection(new Vector3f(0, -1, -1).normalizeLocal(), Vector3f.UNIT_Y);
Sphere s = new Sphere(12, 12, 3);
Geometry g = new Geometry("marker");
g.setMesh(s);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Red);
g.setMaterial(mat);
g.setLocalTranslation(0, -100, 0);
rootNode.attachChild(g);
Geometry g2 = new Geometry("marker");
g2.setMesh(s);
mat.setColor("Color", ColorRGBA.Red);
g2.setMaterial(mat);
g2.setLocalTranslation(10, -100, 0);
rootNode.attachChild(g2);
Geometry g3 = new Geometry("marker");
g3.setMesh(s);
mat.setColor("Color", ColorRGBA.Red);
g3.setMaterial(mat);
g3.setLocalTranslation(0, -100, 10);
rootNode.attachChild(g3);
} }
public void loadHintText() { public void loadHintText() {
@ -110,6 +137,8 @@ public class TerrainTestTile extends SimpleApplication {
* the use of NeighbourFinder. * the use of NeighbourFinder.
* It just links up the left,right,top,bottom TerrainQuads * It just links up the left,right,top,bottom TerrainQuads
* so LOD can work. * so LOD can work.
* It does not implement many of the Terrain interface's methods,
* you will want to do that for your own implementations.
*/ */
private class TiledTerrain extends Node implements Terrain, NeighbourFinder { private class TiledTerrain extends Node implements Terrain, NeighbourFinder {
@ -132,41 +161,43 @@ public class TerrainTestTile extends SimpleApplication {
matTerrain.setFloat("DiffuseMap_0_scale", grassScale); matTerrain.setFloat("DiffuseMap_0_scale", grassScale);
// CREATE THE TERRAIN // CREATE THE TERRAIN
terrain1 = new TerrainQuad("terrain", 65, 513, null); terrain1 = new TerrainQuad("terrain 1", 65, 513, null);
TerrainLodControl control1 = new TerrainLodControl(terrain1, getCamera());
control1.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
terrain1.addControl(control1);
terrain1.setMaterial(matTerrain); terrain1.setMaterial(matTerrain);
terrain1.setLocalTranslation(-256, -100, -256); terrain1.setLocalTranslation(-256, -100, -256);
terrain1.setLocalScale(1f, 1f, 1f); terrain1.setLocalScale(1f, 1f, 1f);
this.attachChild(terrain1); this.attachChild(terrain1);
terrain2 = new TerrainQuad("terrain", 65, 513, null); terrain2 = new TerrainQuad("terrain 2", 65, 513, null);
TerrainLodControl control2 = new TerrainLodControl(terrain2, getCamera());
control2.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
terrain2.addControl(control2);
terrain2.setMaterial(matTerrain); terrain2.setMaterial(matTerrain);
terrain2.setLocalTranslation(-256, -100, 256); terrain2.setLocalTranslation(-256, -100, 256);
terrain2.setLocalScale(1f, 1f, 1f); terrain2.setLocalScale(1f, 1f, 1f);
this.attachChild(terrain2); this.attachChild(terrain2);
terrain3 = new TerrainQuad("terrain", 65, 513, null); terrain3 = new TerrainQuad("terrain 3", 65, 513, null);
TerrainLodControl control3 = new TerrainLodControl(terrain3, getCamera());
control3.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
terrain3.addControl(control3);
terrain3.setMaterial(matTerrain); terrain3.setMaterial(matTerrain);
terrain3.setLocalTranslation(256, -100, -256); terrain3.setLocalTranslation(256, -100, -256);
terrain3.setLocalScale(1f, 1f, 1f); terrain3.setLocalScale(1f, 1f, 1f);
this.attachChild(terrain3); this.attachChild(terrain3);
terrain4 = new TerrainQuad("terrain", 65, 513, null); terrain4 = new TerrainQuad("terrain 4", 65, 513, null);
TerrainLodControl control4 = new TerrainLodControl(terrain4, getCamera());
control4.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
terrain4.addControl(control4);
terrain4.setMaterial(matTerrain); terrain4.setMaterial(matTerrain);
terrain4.setLocalTranslation(256, -100, 256); terrain4.setLocalTranslation(256, -100, 256);
terrain4.setLocalScale(1f, 1f, 1f); terrain4.setLocalScale(1f, 1f, 1f);
this.attachChild(terrain4); this.attachChild(terrain4);
terrain1.setNeighbourFinder(this);
terrain2.setNeighbourFinder(this);
terrain3.setNeighbourFinder(this);
terrain4.setNeighbourFinder(this);
MultiTerrainLodControl lodControl = new MultiTerrainLodControl(getCamera());
lodControl.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
lodControl.addTerrain(terrain1);
lodControl.addTerrain(terrain2);
lodControl.addTerrain(terrain3);// order of these seems to matter
lodControl.addTerrain(terrain4);
this.addControl(lodControl);
} }
/** /**
@ -174,6 +205,7 @@ public class TerrainTestTile extends SimpleApplication {
* 2 4 * 2 4
*/ */
public TerrainQuad getRightQuad(TerrainQuad center) { public TerrainQuad getRightQuad(TerrainQuad center) {
//System.out.println("lookup neighbour");
if (center == terrain1) if (center == terrain1)
return terrain3; return terrain3;
if (center == terrain2) if (center == terrain2)
@ -187,6 +219,7 @@ public class TerrainTestTile extends SimpleApplication {
* 2 4 * 2 4
*/ */
public TerrainQuad getLeftQuad(TerrainQuad center) { public TerrainQuad getLeftQuad(TerrainQuad center) {
//System.out.println("lookup neighbour");
if (center == terrain3) if (center == terrain3)
return terrain1; return terrain1;
if (center == terrain4) if (center == terrain4)
@ -200,6 +233,7 @@ public class TerrainTestTile extends SimpleApplication {
* 2 4 * 2 4
*/ */
public TerrainQuad getTopQuad(TerrainQuad center) { public TerrainQuad getTopQuad(TerrainQuad center) {
//System.out.println("lookup neighbour");
if (center == terrain2) if (center == terrain2)
return terrain1; return terrain1;
if (center == terrain4) if (center == terrain4)
@ -213,6 +247,7 @@ public class TerrainTestTile extends SimpleApplication {
* 2 4 * 2 4
*/ */
public TerrainQuad getDownQuad(TerrainQuad center) { public TerrainQuad getDownQuad(TerrainQuad center) {
//System.out.println("lookup neighbour");
if (center == terrain1) if (center == terrain1)
return terrain2; return terrain2;
if (center == terrain3) if (center == terrain3)

Loading…
Cancel
Save