From c9a5429193e68998f1c693ec2ec70e55a5467455 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Sun, 22 Jan 2017 23:56:22 +0100 Subject: [PATCH] Map: Prep for TerrainQuad enhancements. --- src/roadtrip/view/FineTerrainGrid.java | 574 ++++++++++++++++++ .../view/FineTerrainGridLodControl.java | 79 +++ src/roadtrip/view/GameWorldView.java | 16 +- src/roadtrip/view/TerrainView.java | 2 +- 4 files changed, 666 insertions(+), 5 deletions(-) create mode 100644 src/roadtrip/view/FineTerrainGrid.java create mode 100644 src/roadtrip/view/FineTerrainGridLodControl.java diff --git a/src/roadtrip/view/FineTerrainGrid.java b/src/roadtrip/view/FineTerrainGrid.java new file mode 100644 index 0000000..d80b3a0 --- /dev/null +++ b/src/roadtrip/view/FineTerrainGrid.java @@ -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; + +/** + *

+ * TerrainGrid itself is an actual TerrainQuad. Its four children are the visible four tiles.

+ *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * +-------+-------+
+ * | 1     |     3 |    Four terrainQuads that make up the grid
+ * |    *..|..*    |    with the cameraCell in the middle, covering
+ * |----|--|--|----|    all four quads.
+ * |    *..|..*    |
+ * | 2     |     4 |
+ * +-------+-------+
+ * 

+ * 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. + *

+ * 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. + *

+ * The loading of new tiles triggers events for any TerrainGridListeners. The events are: + *

+ *

+ * These allow physics to update, and other operation (often needed for loading the terrain) to occur + * at the right time. + *

+ * @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 listeners = new HashSet(); + protected Material material; + //cache needs to be 1 row (4 cells) larger than what we care is cached + protected LRUCache cache = new LRUCache(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 xz, List 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(), + 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); + } +} diff --git a/src/roadtrip/view/FineTerrainGridLodControl.java b/src/roadtrip/view/FineTerrainGridLodControl.java new file mode 100644 index 0000000..f433e38 --- /dev/null +++ b/src/roadtrip/view/FineTerrainGridLodControl.java @@ -0,0 +1,79 @@ +/* + * 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.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.terrain.Terrain; +import com.jme3.terrain.geomipmap.TerrainGridListener; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import java.util.List; + +/** + * Updates grid offsets and cell positions. + * As well as updating LOD. + * + * @author sploreg + */ +public class FineTerrainGridLodControl extends TerrainLodControl { + + public FineTerrainGridLodControl(Terrain terrain, Camera camera) { + super(terrain, camera); + } + + @Override + protected void updateLOD(List locations, LodCalculator lodCalculator) { + FineTerrainGrid terrainGrid = (FineTerrainGrid)getSpatial(); + + // for now, only the first camera is handled. + // to accept more, there are two ways: + // 1: every camera has an associated grid, then the location is not enough to identify which camera location has changed + // 2: grids are associated with locations, and no incremental update is done, we load new grids for new locations, and unload those that are not needed anymore + Vector3f cam = locations.isEmpty() ? Vector3f.ZERO.clone() : locations.get(0); + Vector3f camCell = terrainGrid.getCamCell(cam); // get the grid index value of where the camera is (ie. 2,1) + if (terrainGrid.cellsLoaded > 1) { // Check if cells are updated before updating gridoffset. + terrainGrid.gridOffset[0] = Math.round(camCell.x * (terrainGrid.getSize() / 2)); + terrainGrid.gridOffset[1] = Math.round(camCell.z * (terrainGrid.getSize() / 2)); + terrainGrid.cellsLoaded = 0; + } + if (camCell.x != terrainGrid.currentCamCell.x || camCell.z != terrainGrid.currentCamCell.z || !terrainGrid.runOnce) { + // if the camera has moved into a new cell, load new terrain into the visible 4 center quads + terrainGrid.updateChildren(camCell); + for (TerrainGridListener l : terrainGrid.listeners) { + l.gridMoved(camCell); + } + } + terrainGrid.runOnce = true; + super.updateLOD(locations, lodCalculator); + } +} diff --git a/src/roadtrip/view/GameWorldView.java b/src/roadtrip/view/GameWorldView.java index efec816..a95ef60 100644 --- a/src/roadtrip/view/GameWorldView.java +++ b/src/roadtrip/view/GameWorldView.java @@ -35,6 +35,8 @@ import roadtrip.view.model.GameWorldState; */ public class GameWorldView { + public static boolean DEBUG = false;//true; + private final GameWorldState state; private final AssetManager assetManager; @@ -71,6 +73,9 @@ public class GameWorldView { // TERRAIN TEXTURE material terrain.mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); + if (DEBUG) { + terrain.mat_terrain.getAdditionalRenderState().setWireframe(true); + } // Parameters to material: // regionXColorMap: X = 1..4 the texture that should be appliad to state X @@ -143,18 +148,20 @@ public class GameWorldView { ground.addPreFilter(terrain.terrainDataProvider.iterate); - terrain.terrainGrid = new TerrainGrid("terrain", 16 + 1, 512 + 1, new FractalTileLoader(ground, 300f)); + int patchSize = 32; + //terrain.terrainGrid = new TerrainGrid("terrain", 16 + 1, 512 + 1, new FractalTileLoader(ground, 300f)); + terrain.terrainGrid = new FineTerrainGrid("terrain", patchSize + 1, 512 + 1, new FractalTileLoader(ground, 300f)); terrain.terrainGrid.setMaterial(terrain.mat_terrain); //terrain.terrainGrid.setLocalTranslation(0, -200, 0); //terrain.terrainGrid.setLocalScale(2f, 1f, 2f); this.rootNode.attachChild(terrain.terrainGrid); - TerrainLodControl control = new TerrainGridLodControl(terrain.terrainGrid, camera); - control.setLodCalculator(new DistanceLodCalculator(32 + 1, 2.7f)); // patch size, and a multiplier + TerrainLodControl control = new FineTerrainGridLodControl(terrain.terrainGrid, camera); + control.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 3.7f)); // patch size, and a multiplier terrain.terrainGrid.addControl(control); - final TerrainGrid terrainGrid = terrain.terrainGrid; + final FineTerrainGrid terrainGrid = terrain.terrainGrid; terrainGrid.addListener(new TerrainGridListener() { @Override @@ -226,6 +233,7 @@ public class GameWorldView { } }); + /**/ } diff --git a/src/roadtrip/view/TerrainView.java b/src/roadtrip/view/TerrainView.java index 2371f12..733d7da 100644 --- a/src/roadtrip/view/TerrainView.java +++ b/src/roadtrip/view/TerrainView.java @@ -9,7 +9,7 @@ import roadtrip.model.TerrainDataProvider; */ public class TerrainView { public Material mat_terrain; - public TerrainGrid terrainGrid; + public FineTerrainGrid terrainGrid; public float grassScale = 64; public float dirtScale = 64; public float rockScale = 64;