* Cleaned up and simplified the Terrain API.

* Removed dependence on TerrainQuad for TerrainLodControl
* Comments, general cleanup

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7783 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
bre..ns 14 years ago
parent 7cb3ca4712
commit 70f1a6f323
  1. 45
      engine/src/terrain/com/jme3/terrain/Terrain.java
  2. 32
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java
  3. 50
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java
  4. 54
      engine/src/test/jme3test/terrain/TerrainTestReadWrite.java

@ -34,7 +34,6 @@ package com.jme3.terrain;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import java.util.List; import java.util.List;
/** /**
@ -107,20 +106,7 @@ public interface Terrain {
* Infinite or "paged" terrains will not be able to support this, so use with caution. * Infinite or "paged" terrains will not be able to support this, so use with caution.
*/ */
public float[] getHeightMap(); public float[] getHeightMap();
/**
* Tell the terrain system to use/not use Level of Detail algorithms.
* This is allowed to be ignored if a particular implementation cannot support it.
*/
public void useLOD(boolean useLod);
/**
* Check if the terrain is using LOD techniques.
* If a terrain system only supports enabled LOD, then this
* should always return true.
*/
public boolean isUsingLOD();
/** /**
* This is calculated by the specific LOD algorithm. * This is calculated by the specific LOD algorithm.
* A value of one means that the terrain is showing full detail. * A value of one means that the terrain is showing full detail.
@ -130,24 +116,19 @@ public interface Terrain {
public int getMaxLod(); public int getMaxLod();
/** /**
* Called in the update (pre or post, up to you) method of your game. * Called by an LodControl.
* Calculates the level of detail of the terrain and adjusts its geometry. * Calculates the level of detail of the terrain and adjusts its geometry.
* This is where the Terrain's LOD algorithm will change the detail of * This is where the Terrain's LOD algorithm will change the detail of
* the terrain based on how far away this position is from the particular * the terrain based on how far away this position is from the particular
* terrain patch. * terrain patch.
* @param location often the Camera's location * @param location: the Camera's location. A list of one camera location is normal
* if you just have one camera in your scene.
*/ */
public void update(List<Vector3f> location); public void update(List<Vector3f> location);
/**
* Get the spatial instance of this Terrain. Right now just used in the
* terrain editor in JMP.
*/
public Spatial getSpatial();
/** /**
* Lock or unlock the meshes of this terrain. * Lock or unlock the meshes of this terrain.
* Locked meshes are uneditable but have better performance. * Locked meshes are un-editable but have better performance.
* This should call the underlying getMesh().setStatic()/setDynamic() methods. * This should call the underlying getMesh().setStatic()/setDynamic() methods.
* @param locked or unlocked * @param locked or unlocked
*/ */
@ -169,17 +150,15 @@ public interface Terrain {
public Material getMaterial(); public Material getMaterial();
/** /**
* Calculates the percentage along the terrain (in X-Z plane) that the * Used for painting to get the number of vertices along the edge of the
* supplied point (worldX,worldY) is, starting from the x=0, z=0 world * terrain.
* position of the terrain. * This is an un-scaled size, and should represent the vertex count (ie. the
* This method must take into account local translations and scale of the terrain. * texture coord count) along an edge of a square terrain.
* Used for painting onto an alpha image for texture splatting.
* *
* @param worldX world position on X axis * In the standard TerrainQuad default implementation, this will return
* @param worldY world position on Z axis * the "totalSize" of the terrain (512 or so).
* @return a point (U,V in the range [0,1] )
*/ */
public Vector2f getPointPercentagePosition(float worldX, float worldY); public int getTerrainSize();
/** /**
* Get the scale of the texture coordinates. Normally if the texture is * Get the scale of the texture coordinates. Normally if the texture is

@ -41,6 +41,7 @@ import java.util.List;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial; 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;
@ -61,22 +62,27 @@ import java.util.ArrayList;
*/ */
public class TerrainLodControl extends AbstractControl { public class TerrainLodControl extends AbstractControl {
private TerrainQuad terrain; private Terrain terrain;
private List<Camera> cameras; private List<Camera> cameras;
private List<Vector3f> cameraLocations = new ArrayList<Vector3f>(); private List<Vector3f> cameraLocations = new ArrayList<Vector3f>();
public TerrainLodControl() { public TerrainLodControl() {
} }
public TerrainLodControl(Terrain terrain, Camera camera) {
List<Camera> cams = new ArrayList<Camera>();
cams.add(camera);
this.terrain = terrain;
this.cameras = cams;
}
/** /**
* Only uses the first camera right now. * Only uses the first camera right now.
* @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(Terrain terrain, List<Camera> cameras) {
if (terrain instanceof TerrainQuad) { this.terrain = terrain;
this.terrain = (TerrainQuad) terrain;
}
this.cameras = cameras; this.cameras = cameras;
} }
@ -107,11 +113,17 @@ public class TerrainLodControl extends AbstractControl {
cameraClone.add(c); cameraClone.add(c);
} }
} }
return new TerrainLodControl((TerrainQuad) spatial, cameraClone); return new TerrainLodControl((Terrain) spatial, cameraClone);
} }
return null; return null;
} }
public void setCamera(Camera camera) {
List<Camera> cams = new ArrayList<Camera>();
cams.add(camera);
setCameras(cams);
}
public void setCameras(List<Camera> cameras) { public void setCameras(List<Camera> cameras) {
this.cameras = cameras; this.cameras = cameras;
cameraLocations.clear(); cameraLocations.clear();
@ -123,12 +135,12 @@ public class TerrainLodControl extends AbstractControl {
@Override @Override
public void setSpatial(Spatial spatial) { public void setSpatial(Spatial spatial) {
super.setSpatial(spatial); super.setSpatial(spatial);
if (spatial instanceof TerrainQuad) { if (spatial instanceof Terrain) {
this.terrain = (TerrainQuad) spatial; this.terrain = (Terrain) spatial;
} }
} }
public void setTerrain(TerrainQuad terrain) { public void setTerrain(Terrain terrain) {
this.terrain = terrain; this.terrain = terrain;
} }
@ -136,13 +148,13 @@ public class TerrainLodControl extends AbstractControl {
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(terrain, "terrain", null); oc.write((Node)terrain, "terrain", null);
} }
@Override @Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
super.read(im); super.read(im);
InputCapsule ic = im.getCapsule(this); InputCapsule ic = im.getCapsule(this);
terrain = (TerrainQuad) ic.readSavable("terrain", null); terrain = (Terrain) ic.readSavable("terrain", null);
} }
} }

@ -67,7 +67,6 @@ import com.jme3.util.TangentBinormalGenerator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger;
/** /**
* A terrain quad is a node in the quad tree of the terrain system. * A terrain quad is a node in the quad tree of the terrain system.
@ -99,7 +98,6 @@ public class TerrainQuad extends Node implements Terrain {
protected List<Vector3f> lastCameraLocations; // used for LOD calc protected List<Vector3f> lastCameraLocations; // used for LOD calc
private boolean lodCalcRunning = false; private boolean lodCalcRunning = false;
private boolean usingLOD = true;
private int maxLod = -1; private int maxLod = -1;
private HashMap<String,UpdatedTerrainPatch> updatedPatches; private HashMap<String,UpdatedTerrainPatch> updatedPatches;
private final Object updatePatchesLock = new Object(); private final Object updatePatchesLock = new Object();
@ -274,10 +272,6 @@ public class TerrainQuad extends Node implements Terrain {
return 0; return 0;
} }
public Spatial getSpatial() {
return this;
}
public void generateEntropy(ProgressMonitor progressMonitor) { public void generateEntropy(ProgressMonitor progressMonitor) {
// only check this on the root quad // only check this on the root quad
if (isRootQuad()) if (isRootQuad())
@ -312,11 +306,11 @@ public class TerrainQuad extends Node implements Terrain {
public Material getMaterial() { public Material getMaterial() {
// get the material from one of the children. They all share the same material // get the material from one of the children. They all share the same material
if (children != null) { if (children != null) {
for (int i = children.size(); --i >= 0;) { for (int i = children.size(); --i >= 0;) {
Spatial child = children.get(i); Spatial child = children.get(i);
if (child instanceof TerrainQuad) { if (child instanceof TerrainQuad) {
return ((TerrainQuad)child).getMaterial(); return ((TerrainQuad)child).getMaterial();
} else if (child instanceof TerrainPatch) { } else if (child instanceof TerrainPatch) {
return ((TerrainPatch)child).getMaterial(); return ((TerrainPatch)child).getMaterial();
} }
} }
@ -383,12 +377,11 @@ public class TerrainQuad extends Node implements Terrain {
*/ */
private void updateQuadLODs() { private void updateQuadLODs() {
synchronized (updatePatchesLock) { synchronized (updatePatchesLock) {
//if (true)
// return;
if (updatedPatches == null || updatedPatches.isEmpty()) if (updatedPatches == null || updatedPatches.isEmpty())
return; return;
//TODO do the actual geometry update here // do the actual geometry update here
for (UpdatedTerrainPatch utp : updatedPatches.values()) { for (UpdatedTerrainPatch utp : updatedPatches.values()) {
utp.updateAll(); utp.updateAll();
} }
@ -582,7 +575,7 @@ public class TerrainQuad extends Node implements Terrain {
offsetAmount += quarterSize; offsetAmount += quarterSize;
if (lodCalculatorFactory == null) if (lodCalculatorFactory == null)
lodCalculatorFactory = new LodDistanceCalculatorFactory(); // set a default one lodCalculatorFactory = new LodDistanceCalculatorFactory(); // set a default one
// 1 upper left // 1 upper left
float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split); float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split);
@ -690,7 +683,7 @@ public class TerrainQuad extends Node implements Terrain {
int split = (size + 1) >> 1; int split = (size + 1) >> 1;
if (lodCalculatorFactory == null) if (lodCalculatorFactory == null)
lodCalculatorFactory = new LodDistanceCalculatorFactory(); // set a default one lodCalculatorFactory = new LodDistanceCalculatorFactory(); // set a default one
offsetAmount += quarterSize; offsetAmount += quarterSize;
@ -1104,13 +1097,9 @@ public class TerrainQuad extends Node implements Terrain {
return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize); return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize);
} }
public Vector2f getPointPercentagePosition(float worldX, float worldY) {
Vector2f uv = new Vector2f(worldX,worldY); public int getTerrainSize() {
uv.subtractLocal(getLocalTranslation().x, getLocalTranslation().z); // center it on 0,0 return totalSize;
uv.addLocal(totalSize/2, totalSize/2); // shift the bottom left corner up to 0,0
uv.divideLocal(totalSize); // get the location as a percentage
return uv;
} }
@ -1510,10 +1499,6 @@ public class TerrainQuad extends Node implements Terrain {
quadrant = c.readInt("quadrant", 0); quadrant = c.readInt("quadrant", 0);
totalSize = c.readInt("totalSize", 0); totalSize = c.readInt("totalSize", 0);
lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null); lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null);
// the terrain is re-built on load, so we need to run this once
//affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size, Float.MAX_VALUE, size);
//updateNormals();
} }
@Override @Override
@ -1564,14 +1549,6 @@ public class TerrainQuad extends Node implements Terrain {
return maxLod; return maxLod;
} }
public void useLOD(boolean useLod) {
usingLOD = useLod;
}
public boolean isUsingLOD() {
return usingLOD;
}
public int getPatchSize() { public int getPatchSize() {
return patchSize; return patchSize;
} }
@ -1583,9 +1560,6 @@ public class TerrainQuad extends Node implements Terrain {
public float[] getHeightMap() { public float[] getHeightMap() {
//if (true)
// return heightMap;
float[] hm = null; float[] hm = null;
int length = ((size-1)/2)+1; int length = ((size-1)/2)+1;
int area = size*size; int area = size*size;

@ -33,6 +33,7 @@ package jme3test.terrain;
import com.jme3.app.SimpleApplication; import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingBox;
import com.jme3.export.Savable;
import com.jme3.export.binary.BinaryExporter; import com.jme3.export.binary.BinaryExporter;
import com.jme3.export.binary.BinaryImporter; import com.jme3.export.binary.BinaryImporter;
import com.jme3.font.BitmapText; import com.jme3.font.BitmapText;
@ -44,6 +45,8 @@ import com.jme3.material.Material;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.terrain.Terrain;
import com.jme3.terrain.geomipmap.TerrainLodControl; import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad; import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap; import com.jme3.terrain.heightmap.AbstractHeightMap;
@ -69,7 +72,7 @@ import jme3tools.converters.ImageToAwt;
*/ */
public class TerrainTestReadWrite extends SimpleApplication { public class TerrainTestReadWrite extends SimpleApplication {
private TerrainQuad terrain; private Terrain terrain;
protected BitmapText hintText; protected BitmapText hintText;
private float grassScale = 64; private float grassScale = 64;
private float dirtScale = 16; private float dirtScale = 16;
@ -147,8 +150,6 @@ public class TerrainTestReadWrite extends SimpleApplication {
// CREATE HEIGHTMAP // CREATE HEIGHTMAP
AbstractHeightMap heightmap = null; AbstractHeightMap heightmap = null;
try { try {
//heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3);
heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 1f); heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 1f);
heightmap.load(); heightmap.load();
@ -160,15 +161,17 @@ public class TerrainTestReadWrite extends SimpleApplication {
loadTerrain(); loadTerrain();
} else { } else {
// create the terrain as normal, and give it a control for LOD management // create the terrain as normal, and give it a control for LOD management
terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations TerrainQuad terrainQuad = new TerrainQuad("terrain", 65, 129, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations
List<Camera> cameras = new ArrayList<Camera>(); List<Camera> cameras = new ArrayList<Camera>();
cameras.add(getCamera()); cameras.add(getCamera());
TerrainLodControl control = new TerrainLodControl(terrain, cameras); TerrainLodControl control = new TerrainLodControl(terrainQuad, cameras);
terrain.addControl(control); terrainQuad.addControl(control);
terrain.setMaterial(matTerrain); terrainQuad.setMaterial(matTerrain);
terrain.setLocalTranslation(0, -100, 0); terrainQuad.setLocalTranslation(0, -100, 0);
terrain.setLocalScale(2f, 1f, 2f); terrainQuad.setLocalScale(4f, 0.25f, 4f);
rootNode.attachChild(terrain); rootNode.attachChild(terrainQuad);
this.terrain = terrainQuad;
} }
DirectionalLight light = new DirectionalLight(); DirectionalLight light = new DirectionalLight();
@ -211,7 +214,7 @@ public class TerrainTestReadWrite extends SimpleApplication {
fos = new FileOutputStream(new File("terrainsave.jme")); fos = new FileOutputStream(new File("terrainsave.jme"));
// we just use the exporter and pass in the terrain // we just use the exporter and pass in the terrain
BinaryExporter.getInstance().save(terrain, new BufferedOutputStream(fos)); BinaryExporter.getInstance().save((Savable)terrain, new BufferedOutputStream(fos));
fos.flush(); fos.flush();
float duration = (System.currentTimeMillis() - start) / 1000.0f; float duration = (System.currentTimeMillis() - start) / 1000.0f;
@ -237,29 +240,28 @@ public class TerrainTestReadWrite extends SimpleApplication {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
// remove the existing terrain and detach it from the root node. // remove the existing terrain and detach it from the root node.
if (terrain != null) { if (terrain != null) {
terrain.removeFromParent(); Node existingTerrain = (Node)terrain;
terrain.removeControl(TerrainLodControl.class); existingTerrain.removeFromParent();
terrain.detachAllChildren(); existingTerrain.removeControl(TerrainLodControl.class);
existingTerrain.detachAllChildren();
terrain = null; terrain = null;
} }
// import the saved terrain, and attach it back to the root node // import the saved terrain, and attach it back to the root node
fis = new FileInputStream(new File("terrainsave.jme")); File f = new File("terrainsave.jme");
fis = new FileInputStream(f);
BinaryImporter imp = BinaryImporter.getInstance(); BinaryImporter imp = BinaryImporter.getInstance();
imp.setAssetManager(assetManager); imp.setAssetManager(assetManager);
terrain = (TerrainQuad) imp.load(new BufferedInputStream(fis)); terrain = (TerrainQuad) imp.load(new BufferedInputStream(fis));
rootNode.attachChild(terrain); rootNode.attachChild((Node)terrain);
float duration = (System.currentTimeMillis() - start) / 1000.0f; float duration = (System.currentTimeMillis() - start) / 1000.0f;
System.out.println("Load took " + duration + " seconds"); System.out.println("Load took " + duration + " seconds");
// now we have to add back the cameras to the LOD control, since we didn't want to duplicate them on save // now we have to add back the camera to the LOD control
List<Camera> cameras = new ArrayList<Camera>(); TerrainLodControl lodControl = ((Node)terrain).getControl(TerrainLodControl.class);
cameras.add(getCamera()); if (lodControl != null)
TerrainLodControl lodControl = terrain.getControl(TerrainLodControl.class); lodControl.setCamera(getCamera());
if (lodControl != null) {
lodControl.setCameras(cameras);
}
} catch (IOException ex) { } catch (IOException ex) {
Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex);
@ -286,10 +288,10 @@ public class TerrainTestReadWrite extends SimpleApplication {
public void onAction(String name, boolean pressed, float tpf) { public void onAction(String name, boolean pressed, float tpf) {
if (name.equals("clone") && !pressed) { if (name.equals("clone") && !pressed) {
TerrainQuad clone = terrain.clone(); Terrain clone = (Terrain) ((Node)terrain).clone();
terrain.removeFromParent(); ((Node)terrain).removeFromParent();
terrain = clone; terrain = clone;
getRootNode().attachChild(terrain); getRootNode().attachChild((Node)terrain);
} }
} }
}; };

Loading…
Cancel
Save