|
|
@@ -0,0 +1,574 @@ |
|
|
|
/* |
|
|
|
* Copyright (c) 2009-2012 jMonkeyEngine |
|
|
|
* All rights reserved. |
|
|
|
* |
|
|
|
* Redistribution and use in source and binary forms, with or without |
|
|
|
* modification, are permitted provided that the following conditions are |
|
|
|
* met: |
|
|
|
* |
|
|
|
* * Redistributions of source code must retain the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer. |
|
|
|
* |
|
|
|
* * Redistributions in binary form must reproduce the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer in the |
|
|
|
* documentation and/or other materials provided with the distribution. |
|
|
|
* |
|
|
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
|
|
|
* may be used to endorse or promote products derived from this software |
|
|
|
* without specific prior written permission. |
|
|
|
* |
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
|
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
|
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
|
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
*/ |
|
|
|
package roadtrip.view; |
|
|
|
|
|
|
|
import com.jme3.bounding.BoundingBox; |
|
|
|
import com.jme3.export.InputCapsule; |
|
|
|
import com.jme3.export.JmeExporter; |
|
|
|
import com.jme3.export.JmeImporter; |
|
|
|
import com.jme3.export.OutputCapsule; |
|
|
|
import com.jme3.material.Material; |
|
|
|
import com.jme3.math.FastMath; |
|
|
|
import com.jme3.math.Vector2f; |
|
|
|
import com.jme3.math.Vector3f; |
|
|
|
import com.jme3.scene.Spatial; |
|
|
|
import com.jme3.scene.control.UpdateControl; |
|
|
|
import com.jme3.terrain.Terrain; |
|
|
|
import com.jme3.terrain.geomipmap.LRUCache; |
|
|
|
import com.jme3.terrain.geomipmap.NormalRecalcControl; |
|
|
|
import com.jme3.terrain.geomipmap.TerrainGridListener; |
|
|
|
import com.jme3.terrain.geomipmap.TerrainGridTileLoader; |
|
|
|
import com.jme3.terrain.geomipmap.TerrainQuad; |
|
|
|
import com.jme3.terrain.heightmap.HeightMap; |
|
|
|
import com.jme3.terrain.heightmap.HeightMapGrid; |
|
|
|
import java.io.IOException; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.concurrent.Callable; |
|
|
|
import java.util.concurrent.CancellationException; |
|
|
|
import java.util.concurrent.ExecutionException; |
|
|
|
import java.util.concurrent.ExecutorService; |
|
|
|
import java.util.concurrent.Future; |
|
|
|
import java.util.concurrent.LinkedBlockingQueue; |
|
|
|
import java.util.concurrent.ThreadFactory; |
|
|
|
import java.util.concurrent.ThreadPoolExecutor; |
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
import java.util.logging.Level; |
|
|
|
import java.util.logging.Logger; |
|
|
|
|
|
|
|
/** |
|
|
|
* <p> |
|
|
|
* TerrainGrid itself is an actual TerrainQuad. Its four children are the visible four tiles.</p> |
|
|
|
* </p><p> |
|
|
|
* The grid is indexed by cells. Each cell has an integer XZ coordinate originating at 0,0. |
|
|
|
* TerrainGrid will piggyback on the TerrainLodControl so it can use the camera for its |
|
|
|
* updates as well. It does this in the overwritten update() method. |
|
|
|
* </p><p> |
|
|
|
* It uses an LRU (Least Recently Used) cache of 16 terrain tiles (full TerrainQuadTrees). The |
|
|
|
* center 4 are the ones that are visible. As the camera moves, it checks what camera cell it is in |
|
|
|
* and will attach the now visible tiles. |
|
|
|
* </p><p> |
|
|
|
* The 'quadIndex' variable is a 4x4 array that represents the tiles. The center |
|
|
|
* four (index numbers: 5, 6, 9, 10) are what is visible. Each quadIndex value is an |
|
|
|
* offset vector. The vector contains whole numbers and represents how many tiles in offset |
|
|
|
* this location is from the center of the map. So for example the index 11 [Vector3f(2, 0, 1)] |
|
|
|
* is located 2*terrainSize in X axis and 1*terrainSize in Z axis. |
|
|
|
* </p><p> |
|
|
|
* As the camera moves, it tests what cameraCell it is in. Each camera cell covers four quad tiles |
|
|
|
* and is half way inside each one. |
|
|
|
* </p><pre> |
|
|
|
* +-------+-------+ |
|
|
|
* | 1 | 3 | Four terrainQuads that make up the grid |
|
|
|
* | *..|..* | with the cameraCell in the middle, covering |
|
|
|
* |----|--|--|----| all four quads. |
|
|
|
* | *..|..* | |
|
|
|
* | 2 | 4 | |
|
|
|
* +-------+-------+ |
|
|
|
* </pre><p> |
|
|
|
* This results in the effect of when the camera gets half way across one of the sides of a quad to |
|
|
|
* an empty (non-loaded) area, it will trigger the system to load in the next tiles. |
|
|
|
* </p><p> |
|
|
|
* The tile loading is done on a background thread, and once the tile is loaded, then it is |
|
|
|
* attached to the qrid quad tree, back on the OGL thread. It will grab the terrain quad from |
|
|
|
* the LRU cache if it exists. If it does not exist, it will load in the new TerrainQuad tile. |
|
|
|
* </p><p> |
|
|
|
* The loading of new tiles triggers events for any TerrainGridListeners. The events are: |
|
|
|
* <ul> |
|
|
|
* <li>tile Attached |
|
|
|
* <li>tile Detached |
|
|
|
* <li>grid moved. |
|
|
|
* </ul> |
|
|
|
* <p> |
|
|
|
* These allow physics to update, and other operation (often needed for loading the terrain) to occur |
|
|
|
* at the right time. |
|
|
|
* </p> |
|
|
|
* @author Anthyon |
|
|
|
*/ |
|
|
|
public class FineTerrainGrid extends TerrainQuad { |
|
|
|
protected static final Logger log = Logger.getLogger(FineTerrainGrid.class.getCanonicalName()); |
|
|
|
protected Vector3f currentCamCell = Vector3f.ZERO; |
|
|
|
protected int quarterSize; // half of quadSize |
|
|
|
protected int quadSize; |
|
|
|
protected HeightMapGrid heightMapGrid; |
|
|
|
private TerrainGridTileLoader gridTileLoader; |
|
|
|
protected Vector3f[] quadIndex; |
|
|
|
protected Set<TerrainGridListener> listeners = new HashSet<TerrainGridListener>(); |
|
|
|
protected Material material; |
|
|
|
//cache needs to be 1 row (4 cells) larger than what we care is cached |
|
|
|
protected LRUCache<Vector3f, TerrainQuad> cache = new LRUCache<Vector3f, TerrainQuad>(20); |
|
|
|
protected int cellsLoaded = 0; |
|
|
|
protected int[] gridOffset; |
|
|
|
protected boolean runOnce = false; |
|
|
|
protected ExecutorService cacheExecutor; |
|
|
|
|
|
|
|
public int getSize() { |
|
|
|
return size; |
|
|
|
} |
|
|
|
|
|
|
|
protected class UpdateQuadCache implements Runnable { |
|
|
|
|
|
|
|
protected final Vector3f location; |
|
|
|
|
|
|
|
public UpdateQuadCache(Vector3f location) { |
|
|
|
this.location = location; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* This is executed if the camera has moved into a new CameraCell and will load in |
|
|
|
* the new TerrainQuad tiles to be children of this TerrainGrid parent. |
|
|
|
* It will first check the LRU cache to see if the terrain tile is already there, |
|
|
|
* if it is not there, it will load it in and then cache that tile. |
|
|
|
* The terrain tiles get added to the quad tree back on the OGL thread using the |
|
|
|
* attachQuadAt() method. It also resets any cached values in TerrainQuad (such as |
|
|
|
* neighbours). |
|
|
|
*/ |
|
|
|
public void run() { |
|
|
|
for (int i = 0; i < 4; i++) { |
|
|
|
for (int j = 0; j < 4; j++) { |
|
|
|
int quadIdx = i * 4 + j; |
|
|
|
final Vector3f quadCell = location.add(quadIndex[quadIdx]); |
|
|
|
TerrainQuad q = cache.get(quadCell); |
|
|
|
if (q == null) { |
|
|
|
if (heightMapGrid != null) { |
|
|
|
// create the new Quad since it doesn't exist |
|
|
|
HeightMap heightMapAt = heightMapGrid.getHeightMapAt(quadCell); |
|
|
|
q = new TerrainQuad(getName() + "Quad" + quadCell, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap()); |
|
|
|
q.setMaterial(material.clone()); |
|
|
|
log.log(Level.FINE, "Loaded TerrainQuad {0} from HeightMapGrid", q.getName()); |
|
|
|
} else if (gridTileLoader != null) { |
|
|
|
q = gridTileLoader.getTerrainQuadAt(quadCell); |
|
|
|
// only clone the material to the quad if it doesn't have a material of its own |
|
|
|
if(q.getMaterial()==null) q.setMaterial(material.clone()); |
|
|
|
log.log(Level.FINE, "Loaded TerrainQuad {0} from TerrainQuadGrid", q.getName()); |
|
|
|
} |
|
|
|
} |
|
|
|
cache.put(quadCell, q); |
|
|
|
|
|
|
|
|
|
|
|
final int quadrant = getQuadrant(quadIdx); |
|
|
|
final TerrainQuad newQuad = q; |
|
|
|
|
|
|
|
if (isCenter(quadIdx)) { |
|
|
|
// if it should be attached as a child right now, attach it |
|
|
|
getControl(UpdateControl.class).enqueue(new Callable() { |
|
|
|
// back on the OpenGL thread: |
|
|
|
public Object call() throws Exception { |
|
|
|
if (newQuad.getParent() != null) { |
|
|
|
attachQuadAt(newQuad, quadrant, quadCell, true); |
|
|
|
} |
|
|
|
else { |
|
|
|
attachQuadAt(newQuad, quadrant, quadCell, false); |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
}); |
|
|
|
} else { |
|
|
|
getControl(UpdateControl.class).enqueue(new Callable() { |
|
|
|
public Object call() throws Exception { |
|
|
|
removeQuad(newQuad); |
|
|
|
return null; |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
getControl(UpdateControl.class).enqueue(new Callable() { |
|
|
|
// back on the OpenGL thread: |
|
|
|
public Object call() throws Exception { |
|
|
|
for (Spatial s : getChildren()) { |
|
|
|
if (s instanceof TerrainQuad) { |
|
|
|
TerrainQuad tq = (TerrainQuad)s; |
|
|
|
tq.resetCachedNeighbours(); |
|
|
|
} |
|
|
|
} |
|
|
|
System.out.println("fixed normals "+location.clone().mult(size)); |
|
|
|
setNeedToRecalculateNormals(); |
|
|
|
return null; |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected boolean isCenter(int quadIndex) { |
|
|
|
return quadIndex == 9 || quadIndex == 5 || quadIndex == 10 || quadIndex == 6; |
|
|
|
} |
|
|
|
|
|
|
|
protected int getQuadrant(int quadIndex) { |
|
|
|
if (quadIndex == 5) { |
|
|
|
return 1; |
|
|
|
} else if (quadIndex == 9) { |
|
|
|
return 2; |
|
|
|
} else if (quadIndex == 6) { |
|
|
|
return 3; |
|
|
|
} else if (quadIndex == 10) { |
|
|
|
return 4; |
|
|
|
} |
|
|
|
return 0; // error |
|
|
|
} |
|
|
|
|
|
|
|
public FineTerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid, |
|
|
|
Vector2f offset, float offsetAmount) { |
|
|
|
this.name = name; |
|
|
|
this.patchSize = patchSize; |
|
|
|
this.size = maxVisibleSize; |
|
|
|
this.stepScale = scale; |
|
|
|
this.offset = offset; |
|
|
|
this.offsetAmount = offsetAmount; |
|
|
|
initData(); |
|
|
|
this.gridTileLoader = terrainQuadGrid; |
|
|
|
terrainQuadGrid.setPatchSize(this.patchSize); |
|
|
|
terrainQuadGrid.setQuadSize(this.quadSize); |
|
|
|
addControl(new UpdateControl()); |
|
|
|
|
|
|
|
fixNormalEdges(new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2)); |
|
|
|
addControl(new NormalRecalcControl(this)); |
|
|
|
} |
|
|
|
|
|
|
|
public FineTerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid) { |
|
|
|
this(name, patchSize, maxVisibleSize, scale, terrainQuadGrid, new Vector2f(), 0); |
|
|
|
} |
|
|
|
|
|
|
|
public FineTerrainGrid(String name, int patchSize, int maxVisibleSize, TerrainGridTileLoader terrainQuadGrid) { |
|
|
|
this(name, patchSize, maxVisibleSize, Vector3f.UNIT_XYZ, terrainQuadGrid); |
|
|
|
} |
|
|
|
|
|
|
|
public FineTerrainGrid() { |
|
|
|
} |
|
|
|
|
|
|
|
private void initData() { |
|
|
|
int maxVisibleSize = size; |
|
|
|
this.quarterSize = maxVisibleSize >> 2; |
|
|
|
this.quadSize = (maxVisibleSize + 1) >> 1; |
|
|
|
this.totalSize = maxVisibleSize; |
|
|
|
this.gridOffset = new int[]{0, 0}; |
|
|
|
|
|
|
|
/* |
|
|
|
* -z |
|
|
|
* | |
|
|
|
* 1|3 |
|
|
|
* -x ----+---- x |
|
|
|
* 2|4 |
|
|
|
* | |
|
|
|
* z |
|
|
|
*/ |
|
|
|
this.quadIndex = new Vector3f[]{ |
|
|
|
new Vector3f(-1, 0, -1), new Vector3f(0, 0, -1), new Vector3f(1, 0, -1), new Vector3f(2, 0, -1), |
|
|
|
new Vector3f(-1, 0, 0), new Vector3f(0, 0, 0), new Vector3f(1, 0, 0), new Vector3f(2, 0, 0), |
|
|
|
new Vector3f(-1, 0, 1), new Vector3f(0, 0, 1), new Vector3f(1, 0, 1), new Vector3f(2, 0, 1), |
|
|
|
new Vector3f(-1, 0, 2), new Vector3f(0, 0, 2), new Vector3f(1, 0, 2), new Vector3f(2, 0, 2)}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the location in cell-coordinates of the specified location. |
|
|
|
* Cell coordinates are integer corrdinates, usually with y=0, each |
|
|
|
* representing a cell in the world. |
|
|
|
* For example, moving right in the +X direction: |
|
|
|
* (0,0,0) (1,0,0) (2,0,0), (3,0,0) |
|
|
|
* and then down the -Z direction: |
|
|
|
* (3,0,-1) (3,0,-2) (3,0,-3) |
|
|
|
*/ |
|
|
|
public Vector3f getCamCell(Vector3f location) { |
|
|
|
Vector3f tile = getTileCell(location); |
|
|
|
Vector3f offsetHalf = new Vector3f(-0.5f, 0, -0.5f); |
|
|
|
Vector3f shifted = tile.subtract(offsetHalf); |
|
|
|
return new Vector3f(FastMath.floor(shifted.x), 0, FastMath.floor(shifted.z)); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Centered at 0,0. |
|
|
|
* Get the tile index location in integer form: |
|
|
|
* @param location world coordinate |
|
|
|
*/ |
|
|
|
public Vector3f getTileCell(Vector3f location) { |
|
|
|
Vector3f tileLoc = location.divide(this.getWorldScale().mult(this.quadSize)); |
|
|
|
return tileLoc; |
|
|
|
} |
|
|
|
|
|
|
|
public TerrainGridTileLoader getGridTileLoader() { |
|
|
|
return gridTileLoader; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the terrain tile at the specified world location, in XZ coordinates. |
|
|
|
*/ |
|
|
|
public Terrain getTerrainAt(Vector3f worldLocation) { |
|
|
|
if (worldLocation == null) |
|
|
|
return null; |
|
|
|
Vector3f tileCell = getTileCell(worldLocation.setY(0)); |
|
|
|
tileCell = new Vector3f(Math.round(tileCell.x), tileCell.y, Math.round(tileCell.z)); |
|
|
|
return cache.get(tileCell); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the terrain tile at the specified XZ cell coordinate (not world coordinate). |
|
|
|
* @param cellCoordinate integer cell coordinates |
|
|
|
* @return the terrain tile at that location |
|
|
|
*/ |
|
|
|
public Terrain getTerrainAtCell(Vector3f cellCoordinate) { |
|
|
|
return cache.get(cellCoordinate); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Convert the world location into a cell location (integer coordinates) |
|
|
|
*/ |
|
|
|
public Vector3f toCellSpace(Vector3f worldLocation) { |
|
|
|
Vector3f tileCell = getTileCell(worldLocation); |
|
|
|
tileCell = new Vector3f(Math.round(tileCell.x), tileCell.y, Math.round(tileCell.z)); |
|
|
|
return tileCell; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Convert the cell coordinate (integer coordinates) into world coordinates. |
|
|
|
*/ |
|
|
|
public Vector3f toWorldSpace(Vector3f cellLocation) { |
|
|
|
return cellLocation.mult(getLocalScale()).multLocal(quadSize - 1); |
|
|
|
} |
|
|
|
|
|
|
|
protected void removeQuad(TerrainQuad q) { |
|
|
|
if (q != null && ( (q.getQuadrant() > 0 && q.getQuadrant()<5) || q.getParent() != null) ) { |
|
|
|
for (TerrainGridListener l : listeners) { |
|
|
|
l.tileDetached(getTileCell(q.getWorldTranslation()), q); |
|
|
|
} |
|
|
|
q.setQuadrant((short)0); |
|
|
|
this.detachChild(q); |
|
|
|
cellsLoaded++; // For gridoffset calc., maybe the run() method is a better location for this. |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Runs on the rendering thread |
|
|
|
* @param shifted quads are still attached to the parent and don't need to re-load |
|
|
|
*/ |
|
|
|
protected void attachQuadAt(TerrainQuad q, int quadrant, Vector3f quadCell, boolean shifted) { |
|
|
|
|
|
|
|
q.setQuadrant((short) quadrant); |
|
|
|
if (!shifted) |
|
|
|
this.attachChild(q); |
|
|
|
|
|
|
|
Vector3f loc = quadCell.mult(this.quadSize - 1).subtract(quarterSize, 0, quarterSize);// quadrant location handled TerrainQuad automatically now |
|
|
|
q.setLocalTranslation(loc); |
|
|
|
|
|
|
|
if (!shifted) { |
|
|
|
for (TerrainGridListener l : listeners) { |
|
|
|
l.tileAttached(quadCell, q); |
|
|
|
} |
|
|
|
} |
|
|
|
updateModelBound(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Called when the camera has moved into a new cell. We need to |
|
|
|
* update what quads are in the scene now. |
|
|
|
* |
|
|
|
* Step 1: touch cache |
|
|
|
* LRU cache is used, so elements that need to remain |
|
|
|
* should be touched. |
|
|
|
* |
|
|
|
* Step 2: load new quads in background thread |
|
|
|
* if the camera has moved into a new cell, we load in new quads |
|
|
|
* @param camCell the cell the camera is in |
|
|
|
*/ |
|
|
|
protected void updateChildren(Vector3f camCell) { |
|
|
|
|
|
|
|
int dx = 0; |
|
|
|
int dy = 0; |
|
|
|
if (currentCamCell != null) { |
|
|
|
dx = (int) (camCell.x - currentCamCell.x); |
|
|
|
dy = (int) (camCell.z - currentCamCell.z); |
|
|
|
} |
|
|
|
|
|
|
|
int xMin = 0; |
|
|
|
int xMax = 4; |
|
|
|
int yMin = 0; |
|
|
|
int yMax = 4; |
|
|
|
if (dx == -1) { // camera moved to -X direction |
|
|
|
xMax = 3; |
|
|
|
} else if (dx == 1) { // camera moved to +X direction |
|
|
|
xMin = 1; |
|
|
|
} |
|
|
|
|
|
|
|
if (dy == -1) { // camera moved to -Y direction |
|
|
|
yMax = 3; |
|
|
|
} else if (dy == 1) { // camera moved to +Y direction |
|
|
|
yMin = 1; |
|
|
|
} |
|
|
|
|
|
|
|
// Touch the items in the cache that we are and will be interested in. |
|
|
|
// We activate cells in the direction we are moving. If we didn't move |
|
|
|
// either way in one of the axes (say X or Y axis) then they are all touched. |
|
|
|
for (int i = yMin; i < yMax; i++) { |
|
|
|
for (int j = xMin; j < xMax; j++) { |
|
|
|
cache.get(camCell.add(quadIndex[i * 4 + j])); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// --------------------------------------------------- |
|
|
|
// --------------------------------------------------- |
|
|
|
|
|
|
|
if (cacheExecutor == null) { |
|
|
|
// use the same executor as the LODControl |
|
|
|
cacheExecutor = createExecutorService(); |
|
|
|
} |
|
|
|
|
|
|
|
cacheExecutor.submit(new UpdateQuadCache(camCell)); |
|
|
|
|
|
|
|
this.currentCamCell = camCell; |
|
|
|
} |
|
|
|
|
|
|
|
public void addListener(TerrainGridListener listener) { |
|
|
|
this.listeners.add(listener); |
|
|
|
} |
|
|
|
|
|
|
|
public Vector3f getCurrentCell() { |
|
|
|
return this.currentCamCell; |
|
|
|
} |
|
|
|
|
|
|
|
public void removeListener(TerrainGridListener listener) { |
|
|
|
this.listeners.remove(listener); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void setMaterial(Material mat) { |
|
|
|
this.material = mat; |
|
|
|
super.setMaterial(mat); |
|
|
|
} |
|
|
|
|
|
|
|
public void setQuadSize(int quadSize) { |
|
|
|
this.quadSize = quadSize; |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void adjustHeight(List<Vector2f> xz, List<Float> height) { |
|
|
|
Vector3f currentGridLocation = getCurrentCell().mult(getLocalScale()).multLocal(quadSize - 1); |
|
|
|
for (Vector2f vect : xz) { |
|
|
|
vect.x -= currentGridLocation.x; |
|
|
|
vect.y -= currentGridLocation.z; |
|
|
|
} |
|
|
|
super.adjustHeight(xz, height); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
protected float getHeightmapHeight(int x, int z) { |
|
|
|
return super.getHeightmapHeight(x - gridOffset[0], z - gridOffset[1]); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public int getNumMajorSubdivisions() { |
|
|
|
return 2; |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public Material getMaterial(Vector3f worldLocation) { |
|
|
|
if (worldLocation == null) |
|
|
|
return null; |
|
|
|
Vector3f tileCell = getTileCell(worldLocation); |
|
|
|
Terrain terrain = cache.get(tileCell); |
|
|
|
if (terrain == null) |
|
|
|
return null; // terrain not loaded for that cell yet! |
|
|
|
return terrain.getMaterial(worldLocation); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* This will print out any exceptions from the thread |
|
|
|
*/ |
|
|
|
protected ExecutorService createExecutorService() { |
|
|
|
final ThreadFactory threadFactory = new ThreadFactory() { |
|
|
|
public Thread newThread(Runnable r) { |
|
|
|
Thread th = new Thread(r); |
|
|
|
th.setName("jME TerrainGrid Thread"); |
|
|
|
th.setDaemon(true); |
|
|
|
return th; |
|
|
|
} |
|
|
|
}; |
|
|
|
ThreadPoolExecutor ex = new ThreadPoolExecutor(1, 1, |
|
|
|
0L, TimeUnit.MILLISECONDS, |
|
|
|
new LinkedBlockingQueue<Runnable>(), |
|
|
|
threadFactory) { |
|
|
|
protected void afterExecute(Runnable r, Throwable t) { |
|
|
|
super.afterExecute(r, t); |
|
|
|
if (t == null && r instanceof Future<?>) { |
|
|
|
try { |
|
|
|
Future<?> future = (Future<?>) r; |
|
|
|
if (future.isDone()) |
|
|
|
future.get(); |
|
|
|
} catch (CancellationException ce) { |
|
|
|
t = ce; |
|
|
|
} catch (ExecutionException ee) { |
|
|
|
t = ee.getCause(); |
|
|
|
} catch (InterruptedException ie) { |
|
|
|
Thread.currentThread().interrupt(); // ignore/reset |
|
|
|
} |
|
|
|
} |
|
|
|
if (t != null) |
|
|
|
t.printStackTrace(); |
|
|
|
} |
|
|
|
}; |
|
|
|
return ex; |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void read(JmeImporter im) throws IOException { |
|
|
|
super.read(im); |
|
|
|
InputCapsule c = im.getCapsule(this); |
|
|
|
name = c.readString("name", null); |
|
|
|
size = c.readInt("size", 0); |
|
|
|
patchSize = c.readInt("patchSize", 0); |
|
|
|
stepScale = (Vector3f) c.readSavable("stepScale", null); |
|
|
|
offset = (Vector2f) c.readSavable("offset", null); |
|
|
|
offsetAmount = c.readFloat("offsetAmount", 0); |
|
|
|
gridTileLoader = (TerrainGridTileLoader) c.readSavable("terrainQuadGrid", null); |
|
|
|
material = (Material) c.readSavable("material", null); |
|
|
|
initData(); |
|
|
|
if (gridTileLoader != null) { |
|
|
|
gridTileLoader.setPatchSize(this.patchSize); |
|
|
|
gridTileLoader.setQuadSize(this.quadSize); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void write(JmeExporter ex) throws IOException { |
|
|
|
super.write(ex); |
|
|
|
OutputCapsule c = ex.getCapsule(this); |
|
|
|
c.write(gridTileLoader, "terrainQuadGrid", null); |
|
|
|
c.write(size, "size", 0); |
|
|
|
c.write(patchSize, "patchSize", 0); |
|
|
|
c.write(stepScale, "stepScale", null); |
|
|
|
c.write(offset, "offset", null); |
|
|
|
c.write(offsetAmount, "offsetAmount", 0); |
|
|
|
c.write(material, "material", null); |
|
|
|
} |
|
|
|
} |