Where to use Callables to thread procedural terrain generation?

I’ve taken a liking to the old TerrainGrid demos that demonstrate the capabilities of procedural terrain generation. The loading in those demos are very seamless and I wish to similarly allow for that in some other programs built also for procedural terrain generation. Unfortunately, larger projects have become so chock full of various functions that it’s immensely difficult to sit down and determine where to implement Callables to alleviate the performance bottlenecks of generating procedural terrain.

For this troubleshooting, I’m tinkering with a very shoddy voxel program found here: GitHub - DylanDuPont/VoxelTest: Voxels

If anyone’s able to point me in the right direction, it would be very much appreciated!

You could take a look at the work in progress by @jayfella which can be found here: https://github.com/jayfella/World

The code does caching and loading in external threads and is IMO very well done.

Important Note: as I said, he is not done with this project yet and the code will have to be modified to work at all in its current stage…that said, I made it work for a game I am developing right now and I really like it!

Things that have to be modified to make the code work:

  1. Creating PhysicsControls for the new TerrainQuads at the appropriate places
  2. Adding a few lines to make the worldScale factor work.

Important Note #2: I have so far only modified and tested the ImageHeightMap implementation and haven’t really looked at the NoiseHeightMap yet, since I don’t need it for my game.

I am also still a Java noob, so I don’t know if the changes I made are appropriate; they just work for me…I also don’t know if posting the modified code is ok for @jayfella since it is all his work and isn’t finished yet…

Feel free - Anything I write on GitHub is free to use and break :slight_smile:

Cool, well in that case let me know if you want the modified version @mjbmitch…the whole thing is a bit long to post here :slight_smile:

@BloodyJ said: Cool, well in that case let me know if you want the modified version @mjbmitch...the whole thing is a bit long to post here :)

It would be amazing if you could send me a link to it. I have to agree with you that the terrain caching and loading works wonders. I was actually quite surprised to see that, aside from the jMonkey library, the program’s code was split amongst a mere 4 classes.

I’ll have to do some additional research to figure out how to implement voxels into the program. :smiley:

I actually had a look at the NoiseHeightMap example in the meantime and it doesn’t seem to need further modification, so if you’re just using that one you only have to change one little error in World.java:
At line 109 change
[java] public Node getCachedItem(Vector3f location) { return this.activeItems.get(location); }[/java]

to

[java] public Node getCachedItem(Vector3f location) { return this.itemsCache.get(location); }[/java]

plus there seem to be some redundant lines that set the ShadowMode, but you can leave those in, as I’m not 100% that they are redundant…

If you’re using custom HeightMaps, you will want the ImageHeightMap example, which needs some modifications in the getWorldItem() method:

[java]@Override
public Node getWorldItem(Vector3f location)
{
// check if the requested terrain is already loaded.
Node tq = this.getLoadedItem(location);
if (tq != null) return tq;

    // check if the requested terrain is already cached.
    tq = (TerrainQuad)this.getCachedItem(location);
    if (tq != null) return tq;
    
    // doesnt exist anywhere, so we'll create it.
    String tqName = "TerrainQuad_" + (int)location.getX() + "_" + (int)location.getZ();

    String imagePath = new StringBuilder()
            .append("Textures/heightmaps/terrain_")
            .append((int)location.getX())
            .append("_")
            .append((int)location.getZ())
            .append(".png")
            .toString();
    
    try
    {
        Texture hmapImage = this.getApplication().getAssetManager().loadTexture(imagePath);
        AbstractHeightMap map = new ImageBasedHeightMap(hmapImage.getImage());
        map.load();

        tq = new TerrainQuad(tqName, this.getPatchSize(), this.getBlockSize(), map.getHeightMap());
    
    tq.setLocalTranslation(this.fromTerrainLocation(location).multLocal(this.getWorldScale()));			// added by me
    tq.setLocalScale(this.getWorldScale(), 1, this.getWorldScale());                                                       // added by me
    
    tq.addControl(new RigidBodyControl(new HeightfieldCollisionShape(map.getHeightMap(), tq.getLocalScale()), 0));    // added by me
    
    }
    catch (AssetNotFoundException ex)
    {
        Logger.getLogger("com.jme").log(Level.INFO, "Image not found: {0}", imagePath);
    }
    
    return tq;
}[/java]

have fun :slight_smile:

Slightly off-topic (and this might be more related to the Terrain’s use of Materials) but I have some weird seeming issues at the borders of each chunk. In this screenshot I have the following code in case there might have been ideas of it interpolating only with the textures in the same chunk:

[java]texture.setMagFilter(MagFilter.Nearest);[/java]

Hmm that’s a question for someone a little more advanced than me^^

For me the terrain texture seams are fine; I’m using the modified ImageHeightMap with seamless textures…

@BloodyJ said: Hmm that's a question for someone a little more advanced than me^^

For me the terrain texture seams are fine; I’m using the modified ImageHeightMap with seamless textures…

Weirdly enough, I am using ImageHeightMap for my terrain but it looks like the textures the terrain is using lacks precision in terms of the location it’s placing them at.

Is the ImageHeightMap created through an actual image (which might subsequently be created via noise)? I’ve had suspicion that this would be a slower method to create terrain compared to raw noise data (possibly being bottlenecked at the operating system’s file creation/deletion, etc.).

If the offer still stands, I’m really interested in seeing your implementation of the code (and now curious as to determine if you have the texture quirk that I’m having).

I actually posted my code already, but I see how it might all be a bit confusing, so let me explain:

I’m using jayfella’s IWorld.java, World.java and Example_ImageHeightMap.java and added the stuff from his Main.java to my Main class (all of that can be found here)

Now, there are two things you have to do to make this work (as it won’t otherwise):

  1. Make the modifications I posted above (change that one line in World.java and replace the getWorldItem() method in the Example_ImageHeightMap.java with the one I posted.)

  2. Because World.java is written to supply its own ThreadPoolExecutor to the LODControl and the LODControl from the jme3 stable build doesn’t support that, you have to use the svn build of jme3…if you don’t want to use svn, you need to switch out the 3 LODControl classes for the ones from svn (both options work for me…right now I am just using the svn build though).

If it still doesn’t work for you, then I can’t help you unfortunately as I’m still quite new at this myself…this is how I got it to work…

Possible reasons for it not working off the top of my head: Did you make sure the textures are tileable (seamless)? Did you set the material correctly? Example from my implementation which is using an alphaMap and 4 textures for splatting:

[java] Material material = new Material(this.getApplication().getAssetManager(), “Common/MatDefs/Terrain/TerrainLighting.j3md”);

Texture base = this.getApplication().getAssetManager().loadTexture("Textures/r.jpg");
base.setWrap(Texture.WrapMode.Repeat);
material.setTexture("DiffuseMap", base);
material.setFloat("DiffuseMap_0_scale", 16);

Texture alt = this.getApplication().getAssetManager().loadTexture("Textures/g.png");
alt.setWrap(Texture.WrapMode.Repeat);
material.setTexture("DiffuseMap_1", alt);
material.setFloat("DiffuseMap_1_scale", 64);

Texture wall = this.getApplication().getAssetManager().loadTexture("Textures/b.jpg");
wall.setWrap(Texture.WrapMode.Repeat);
material.setTexture("DiffuseMap_2", wall);
material.setFloat("DiffuseMap_2_scale", 128);

Texture alphaCh = this.getApplication().getAssetManager().loadTexture("Textures/a.jpg");
alphaCh.setWrap(Texture.WrapMode.Repeat);
material.setTexture("DiffuseMap_3", alphaCh);
material.setFloat("DiffuseMap_3_scale", 128);

material.setTexture("AlphaMap", this.getApplication().getAssetManager().loadTexture("Textures/Terrain/alpha.png"));[/java]

Other ideas: leave the patchsize at 65 to be safe. Make sure your heightmaps are correctly named and have the right size (should be the same as your blocksize), leave the worldscale at 1 for testing.

Can’t think of anything else…

Note: I just skimmed your post again;

Is the ImageHeightMap created through an actual image (which might subsequently be created via noise)? I’ve had suspicion that this would be a slower method to create terrain compared to raw noise data (possibly being bottlenecked at the operating system’s file creation/deletion, etc.).

If you use ImageHeightMap you have to supply your own heightmaps!!..in contrast to the NoiseHeightMap this class will not generate them for you…I think this might be the problem you are having.

Okay, so I’ve determined that my problem has something to do with either of using TerrainQuads instead of TerrainGrids (as the TerrainGrid demo works fine) or the fact that I have not ever downloaded the SVN, which may have fixed some bugs related to what I’m experiencing.

I’ll give you all an update as to whether that fixed it.

I assume too much :roll:

I thought you were using jayfella’s world implementation…it has nothing to do with terrain grid…

TerrainGrid is the old implementation of quasi-endless terrain, which will be deprecated in a couple of months (this info is from a recent forum thread…afaik Splorek said something like that)

Therefore, you are better off using a newer implementation as there are several problems with the old one (by old one I mean TerrainGrid) and it isn’t being maintained any longer.

That is why I pointed you in the direction of jayfella’s new implementation in the first place. As I said it’s not yet finished, but it already works better than TerrainGrid IMO. In order to use it follow the instructions of my previous posts in this thread.

So again, I am not using TerrainGrid at all, I am using jayfells’s implementation along with my aforementioned modifications. His implementation is currently based on TerrainQuads, if you will.

@BloodyJ said: I assume too much :roll:

I thought you were using jayfella’s world implementation…it has nothing to do with terrain grid…

TerrainGrid is the old implementation of quasi-endless terrain, which will be deprecated in a couple of months (this info is from a recent forum thread…afaik Splorek said something like that)

Therefore, you are better off using a newer implementation as there are several problems with the old one (by old one I mean TerrainGrid) and it isn’t being maintained any longer.

That is why I pointed you in the direction of jayfella’s new implementation in the first place. As I said it’s not yet finished, but it already works better than TerrainGrid IMO. In order to use it follow the instructions of my previous posts in this thread.

So again, I am not using TerrainGrid at all, I am using jayfells’s implementation along with my aforementioned modifications. His implementation is currently based on TerrainQuads, if you will.

I’m using all of that for my own program as well. It just seems that there are problems with the textures for some reason.

EDIT:
I figured out my issue has something to do with the normals on the geometry. I’m experiencing a problem very similar to this post, yet I have not altered any vertices or normals: http://hub.jmonkeyengine.org/forum/topic/jmonkey-3-is-ignoring-mesh-vertex-normals/

@mjbmitch I think your problem is either you are naming the images in the wrong direction or you arent specifying the correct image size.

As shown below, the images should be named in that fashion. Notice the Z plane increments from bottom to top, not the other way around.

I apologize in advance for making such a long post!

I’m clueless about this problem. If I’m having the problem it probably means someone else is too, so I guess I’ll post my code and hopefully someone might be able to catch the bug.

For those of you that need a refresher, there’s an issue with the texture not being seamless at some of the TerrainQuad intersections. I’ve disabled LoD for my code but I do not believe that is an issue. If you’re interested in helping find the issue, please copy and paste the code into your IDE and look around the world for weird texture seams.

[java]
package game;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.system.AppSettings;
import terrain.World;
import terrain.gen.NoiseHeightMapA;

/**

  • @author jayfella

  • @author mjbmitch
    */
    public class Main extends SimpleApplication
    {
    private BulletAppState bulletAppState;
    private World world;

    public static void main(String[] args)
    {
    Main app = new Main();
    AppSettings settings = new AppSettings(true);
    settings.setResolution(640, 480);
    settings.setVSync(true);
    app.setSettings(settings);
    app.setShowSettings(false);
    app.start();
    }

    @Override
    public void simpleInitApp()
    {
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);

     // 1 mph = 0.44704f
     float camSpeed = 0.44704f * 300; // 300 mph
     this.flyCam.setMoveSpeed(camSpeed);
     
     // add the sun
     DirectionalLight sun = new DirectionalLight();
     sun.setColor(ColorRGBA.Yellow);
     sun.setDirection(new Vector3f(-1f, 0, 0));
     //rootNode.addLight(sun);
     
     // add some ambient light
     AmbientLight ambientLight = new AmbientLight();
     ambientLight.setColor(ColorRGBA.White);
     //rootNode.addLight(ambientLight);
     
     // set the sky color
     this.viewPort.setBackgroundColor(new ColorRGBA(0.357f, 0.565f, 0.878f, 1f));
     
     /*int patchSize = 17;
     int blockSize = 33;
     int worldHeight = 256;
     int worldScale = 4;*/
     
     int patchSize = 65;
     int blockSize = 129;
     //int worldHeight = 256;
     float worldHeight = 256f;
     int worldScale = 1;
     
     world = new NoiseHeightMapA(this, patchSize, blockSize, worldHeight, worldScale);
     //world = new ImageHeightMapA(this, patchSize, blockSize, worldHeight, worldScale);
     
     world.setViewDistance((byte) 5);
     
     stateManager.attach(world);
    

    }

    private boolean hasJoined = false;

    @Override
    public void simpleUpdate(float tpf)
    {
    // wait until the world has loaded until we let the player join.
    if (world == null || world.isLoaded() == false || hasJoined)
    return;

     this.getCamera().setLocation(new Vector3f(0, 250, 0));
     this.hasJoined = true;
    

    }

    @Override
    public void destroy()
    {
    super.destroy();

     // clean up
     if (world != null)
     	world.close();
    

    }
    }
    [/java]

[java]
package terrain;

import com.jme3.math.Vector3f;
import com.jme3.scene.Node;

/**

  • @author jayfella

  • @author mjbmitch
    */
    public interface Dimension
    {
    /void setViewDistance(int north, int east, int south, int west);
    void setViewDistance(int distance);
    /

    // added
    void setViewDistance(byte north, byte east, byte south, byte west);
    void setViewDistance(byte distance);

    /int getViewDistanceNorth();
    int getViewDistanceEast();
    int getViewDistanceSouth();
    int getViewDistanceWest();
    /

    // added
    byte getViewDistanceNorth();
    byte getViewDistanceEast();
    byte getViewDistanceSouth();
    byte getViewDistanceWest();

    float getWorldHeight();

    int getThreadPoolCount();
    void setThreadPoolCount(int threadcount);

    int getPatchSize();
    int getBlockSize();

    boolean worldItemLoaded(Node node);
    boolean worldItemUnloaded(Node node);

    Node getWorldItem(Vector3f location);

    boolean isLoaded();

    int getWorldScale();

    Node getLoadedItem(Vector3f location);
    Node getCachedItem(Vector3f location);
    }
    [/java]

[java]
package terrain;

import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.bullet.BulletAppState;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.terrain.geomipmap.TerrainGridLodControl;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import java.io.Closeable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;

/**

  • @author jayfella

  • @author BloodyJ

  • @author mjbmitch
    */
    public abstract class World extends AbstractAppState implements Dimension, Closeable
    {
    private final SimpleApplication app;

    //private int vd_north = 2, vd_east = 2, vd_south = 2, vd_west = 2;
    private byte vd_north_b = 2, vd_east_b = 2, vd_south_b = 2, vd_west_b = 2; // added

    private int oldLocX = Integer.MAX_VALUE, oldLocZ = Integer.MAX_VALUE;

    private int positionAdjustment = 0;
    private int topLx, topLz, botRx, botRz;

    private int totalVisibleChunks = 25;
    private int worldScale;

    private boolean isLoaded = false;
    private volatile boolean cacheInterrupted = false;

    private int patchSize, blockSize;
    private float worldHeight = 256f;

    private final ScheduledThreadPoolExecutor threadpool = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors());

    private final Set<Vector3f> itemsQue = new HashSet<Vector3f>();
    private final Map<Vector3f, Node> itemsCache = new ConcurrentHashMap<Vector3f, Node>();

    private final ConcurrentLinkedQueue<Node> newItems = new ConcurrentLinkedQueue<Node>();
    private final Map<Vector3f, Node> activeItems = new HashMap<Vector3f, Node>();

    // used for procedural terrain (image, noise, etc.)
    public World(SimpleApplication app, int patchSize, int blockSize, float height, int worldScale)
    {
    this.app = app;
    this.patchSize = patchSize;
    this.blockSize = blockSize;
    this.worldHeight = height;
    this.worldScale = worldScale;
    this.positionAdjustment = (blockSize - 1) / 2;
    }

    // used for pre-created scenes
    public World(SimpleApplication app, Spatial scene)
    {
    this.app = app;
    }

    public SimpleApplication getApplication() { return this.app; }

    /*public void setViewDistance(int north, int east, int south, int west)
    {
    this.vd_north = north;
    this.vd_east = east;
    this.vd_south = south;
    this.vd_west = west;

     totalVisibleChunks = (vd_west + vd_east + 1) * (vd_north + vd_south + 1);
    

    }

    public void setViewDistance(int distance)
    {
    setViewDistance(distance, distance, distance, distance);
    }*/

    // added
    public void setViewDistance(byte north, byte east, byte south, byte west)
    {
    this.vd_north_b = north;
    this.vd_east_b = east;
    this.vd_south_b = south;
    this.vd_west_b = west;

     totalVisibleChunks = (vd_west_b + vd_east_b + 1) * (vd_north_b + vd_south_b + 1);
    

    }

    // added
    public void setViewDistance(byte distance)
    {
    setViewDistance(distance, distance, distance, distance);
    }

    /public int getViewDistanceNorth() { return vd_north; }
    public int getViewDistanceEast() { return vd_east; }
    public int getViewDistanceSouth() { return vd_south; }
    public int getViewDistanceWest() { return vd_west; }
    /

    // added
    public byte getViewDistanceNorth() { return vd_north_b; }
    public byte getViewDistanceEast() { return vd_east_b; }
    public byte getViewDistanceSouth() { return vd_south_b; }
    public byte getViewDistanceWest() { return vd_west_b; }

    public float getWorldHeight() { return this.worldHeight; }

    public int getThreadPoolCount() { return threadpool.getPoolSize(); }
    public void setThreadPoolCount(int threadcount) { threadpool.setCorePoolSize(threadcount); }

    protected final int getPositionAdjustment() { return this.positionAdjustment; }
    protected final void setPositionAdjustment(int value) { this.positionAdjustment = value; }

    public int getPatchSize() { return this.patchSize; }
    public int getBlockSize() { return this.blockSize; }

    public abstract boolean worldItemLoaded(Node node);
    public abstract boolean worldItemUnloaded(Node node);
    public abstract Node getWorldItem(Vector3f location);

    public boolean isLoaded() { return this.isLoaded; }

    public int getWorldScale() { return this.worldScale; }

    public Node getLoadedItem(Vector3f location) { return this.activeItems.get(location); }
    public Node getCachedItem(Vector3f location) { return this.itemsCache.get(location); }

    /**

    • Precondition: blockSize is equal to 2^n + 1

    • @param blockSize

    • @return The number 2 is raised to in order to equal blockSize - 1
      */
      private int bitCalc(int blockSize)
      {
      switch (blockSize)
      {
      case 17: return 4;
      case 33: return 5;
      case 65: return 6;
      case 129: return 7;
      case 257: return 8;
      case 513: return 9;
      case 1025: return 10;
      }

      throw new IllegalArgumentException(“Invalid block size specified.”);
      }

    public int getBitShiftCount() { return this.bitCalc(blockSize); }

    public Vector3f toTerrainLocation(Vector3f location)
    {
    int x = (int) location.getX() >> this.getBitShiftCount();
    int z = (int) location.getZ() >> this.getBitShiftCount();

     return new Vector3f(x, 0, z);
    

    }

    public Vector3f fromTerrainLocation(Vector3f location)
    {
    int x = (int) location.getX() << this.getBitShiftCount();
    int z = (int) location.getZ() << this.getBitShiftCount();

     return new Vector3f(x, 0, z);
    

    }

    // ===== Algorithms Start Here ===== //

    private boolean checkForOldItems()
    {
    Iterator<Map.Entry<Vector3f, Node>> iterator = activeItems.entrySet().iterator();

     while(iterator.hasNext())
     {
     	Map.Entry&lt;Vector3f, Node&gt; entry = iterator.next();
     	Vector3f quadLocation = entry.getKey();
     	
     	if (quadLocation.getX() &lt; topLx || quadLocation.getX() &gt; botRx || quadLocation.getZ() &lt; topLz || quadLocation.getZ() &gt; botRz)
     	{
     		TerrainQuad chunk = (TerrainQuad) entry.getValue();
     		
     		if (!this.worldItemUnloaded(chunk))
     			return false;
     		
     		app.getStateManager().getState(BulletAppState.class).getPhysicsSpace().remove(chunk);
     		app.getRootNode().detachChild(chunk);
     		
     		iterator.remove();
     		
     		return true;
     	}
     }
     
     return false;
    

    }

    private boolean checkForNewItems()
    {
    // tiles are always removed first to keep triangle count down
    if (activeItems.size() == totalVisibleChunks)
    {
    isLoaded = true; // used to determine whether the player can join the world

     	return false;
     }
     
     // check if any requested tiles are ready to be added
     Node pending = newItems.poll();
     
     if (pending != null)
     {
     	if (!worldItemLoaded(pending))
     		return false;
     	
     	TerrainLodControl lodControl = new TerrainLodControl((TerrainQuad) pending, app.getCamera());
     	//lodControl.setExecutor(threadpool);
     	lodControl.setLodCalculator(new DistanceLodCalculator(patchSize, 0f));
     	//pending.addControl(lodControl);
     	
     	Vector3f scaledPos = new Vector3f(pending.getWorldTranslation().getX() / this.getWorldScale(), 0, pending.getWorldTranslation().getZ() / this.getWorldScale());
     	
     	// TODO: Does the order matter?
     	app.getRootNode().attachChild(pending);
     	app.getStateManager().getState(BulletAppState.class).getPhysicsSpace().add(pending);
     	activeItems.put(this.toTerrainLocation(scaledPos), pending);
     	
     	return true;
     }
     else
     {
     	for (int x = topLx; x &lt;= botRx; x++)
     	{
     		for (int z = topLz; z &lt;= botRz; z++)
     		{
     			final Vector3f location = new Vector3f(x, 0, z);
     			
     			// check it's already loaded
     			if (activeItems.get(location) != null)
     				continue;
     			
     			// check if it's already in the queue
     			if (itemsQue.contains(location))
     				continue;
     			
     			// check if it's already in the cache
     			Node chunk = itemsCache.get(location);
     			
     			if (chunk != null) // exists
     			{
     				if (!this.worldItemLoaded(chunk))
     					return false;
     				
     				TerrainLodControl lodControl = new TerrainLodControl((TerrainQuad) chunk, app.getCamera());
     				//lodControl.setExecutor((threadpool);
     				lodControl.setLodCalculator(new DistanceLodCalculator(patchSize, 0f));
     				//chunk.addControl(lodControl);
     				
     				//chunk.setShadowMode(ShadowMode.Receive);
     				
     				app.getRootNode().attachChild(chunk);
     				app.getStateManager().getState(BulletAppState.class).getPhysicsSpace().add(chunk);
     				activeItems.put(location, chunk);
     				
     				return true;
     			}
     			else // doesn't exist; generate it
     			{
     				itemsQue.add(location);
     				
     				threadpool.submit(new Runnable()
     				{
     					@Override
     					public void run()
     					{
     						Node newChunk = getWorldItem(location);
     						
     						if (newChunk != null)
     						{
     							newItems.add(newChunk);
     							
     							app.enqueue(new Callable&lt;Boolean&gt;()
     							{
     								public Boolean call()
     								{
     									itemsQue.remove(location);
     									
     									return true;
     								}
     							});
     						}
     					}
     				});
    
     				return true;
     			}
     		}
     	}
     }
    
     return false;
    

    }

    private void recalculateCache()
    {
    itemsCache.clear();
    cacheInterrupted = false;

     Runnable cacheUpdater = new Runnable()
     {
     	@Override
     	public void run()
     	{
     		// top and bottom
     		for (int x = (topLx - 1); x &lt;= (botRx + 1); x++)
     		{
     			if (cacheInterrupted)
     				return;
     			
     			// top
     			final Vector3f topLocation = new Vector3f(x, 0, topLz - 1);
     			final Node topChunk = getWorldItem(topLocation);
     			//topChunk.setShadowMode(ShadowMode.Receive);
     			
     			// bottom
     			final Vector3f bottomLocation = new Vector3f(x, 0, botRz + 1);
     			final Node bottomChunk = getWorldItem(bottomLocation);
     			//bottomChunk.setShadowMode(ShadowMode.Receive);
     			
     			app.enqueue(new Callable&lt;Boolean&gt;()
     			{
     				public Boolean call()
     				{
     					itemsCache.put(topLocation, topChunk);
     					itemsCache.put(bottomLocation, bottomChunk);
     					
     					return true;
     				}
     			});
     		}
     		
     		// sides
     		for (int z = topLz; z &lt;= botRz; z++)
     		{
     			if (cacheInterrupted)
     				return;
     			
     			// left
     			final Vector3f leftLocation = new Vector3f(topLx, 0, z);
     			final Node leftChunk = getWorldItem(leftLocation);
     			//leftChunk.setShadowMode(ShadowMode.Receive);
     			
     			// right
     			final Vector3f rightLocation = new Vector3f(botRx + 1, 0, z);
     			final Node rightChunk = getWorldItem(rightLocation);
     			//rightChunk.setShadowMode(ShadowMode.Receive);
     			
     			app.enqueue(new Callable&lt;Boolean&gt;()
     			{
     				public Boolean call()
     				{
     					itemsCache.put(leftLocation, leftChunk);
     					itemsCache.put(rightLocation, rightChunk);
     					
     					return true;
     				}
     			});
     		}
     	}
     };
     
     threadpool.execute(cacheUpdater);
    

    }

    // ===== Algorithms End Here ===== //

    // TODO: Hardcode the actualX and actualZ variables into the computations.
    @Override
    public void update(float tpf)
    {
    // TODO: Does the variable declarations need parenthesis and/or does it need to be casted?
    int actualX = (int) (app.getCamera().getLocation().getX() + positionAdjustment);
    int actualZ = (int) (app.getCamera().getLocation().getZ() + positionAdjustment);

     // TODO: Does the variable declarations really need to be cast?
     int locX = (int) actualX &gt;&gt; this.getBitShiftCount();
     int locZ = (int) actualZ &gt;&gt; this.getBitShiftCount();
     
     locX /= worldScale;
     locZ /= worldScale;
     
     if (locX == oldLocX &amp;&amp; locZ == oldLocZ &amp;&amp; itemsQue.isEmpty() &amp;&amp; newItems.isEmpty())
     	return;
     
     topLx = locX - vd_west_b;
     topLz = locZ - vd_north_b;
     botRx = locX + vd_east_b;
     botRz = locZ + vd_south_b;
     
     if (checkForOldItems())
     	return;
     
     if (checkForNewItems())
     	return;
     
     if (itemsQue.isEmpty() &amp;&amp; newItems.isEmpty())
     {
     	cacheInterrupted = true;
     	recalculateCache();
     	
     	oldLocX = locX;
     	oldLocZ = locZ;
     }
    

    }

    @Override
    public void close()
    {
    threadpool.shutdown();
    }
    }
    [/java]

[java]
package terrain.gen;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Node;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.noise.ShaderUtils;
import com.jme3.terrain.noise.basis.FilteredBasis;
import com.jme3.terrain.noise.filter.IterativeFilter;
import com.jme3.terrain.noise.filter.OptimizedErode;
import com.jme3.terrain.noise.filter.PerturbFilter;
import com.jme3.terrain.noise.filter.SmoothFilter;
import com.jme3.terrain.noise.fractal.FractalSum;
import com.jme3.terrain.noise.modulator.NoiseModulator;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.WrapMode;
import java.nio.FloatBuffer;
import terrain.World;

/**

  • @author jayfella

  • @author mjbmitch
    */
    public class NoiseHeightMapA extends World
    {
    private final FilteredBasis filteredBasis;
    private final Material terrainMaterial;

    private float grassScale = 64f;
    private float dirtScale = 16f;
    private float rockScale = 128f;

    public NoiseHeightMapA(SimpleApplication app, int patchSize, int blockSize, float worldHeight, int worldScale)
    {
    super(app, patchSize, blockSize, worldHeight, worldScale);

     this.filteredBasis = createNoiseGenerator(); // jME noise generator
     this.terrainMaterial = createTerrainMaterial(); // universal material to apply to all terrain
    

    }

    @Override
    public boolean worldItemLoaded(Node node)
    {
    node.setMaterial(terrainMaterial);
    //node.setShadowMode(ShadowMode.Receive);

     // return true if we want to allow this terrain to load
     // return false if we want to cancel this terrain loading
     return true;
    

    }

    @Override
    public boolean worldItemUnloaded(Node node)
    {
    // return true if we want to allow this terrain to unload
    // return false if we want to cancel this terrain from unloading
    return true;
    }

    @Override
    public Node getWorldItem(Vector3f location)
    {
    // check if the requested terrain is already loaded
    if (this.getLoadedItem(location) != null)
    return this.getLoadedItem(location);

     // check if the requested terrain is already cached
     if (this.getCachedItem(location) != null)
     	return this.getCachedItem(location);
     
     // doesn't exist; generate it
     String tqName = "TerrainQuad_" + (int) location.getX() + "_" + (int) location.getZ();
     float[] heightmap = getHeightmap((int) location.getX(), (int) location.getZ());
     
     Node tq = new TerrainQuad(tqName, this.getPatchSize(), this.getBlockSize(), heightmap);
     tq.setLocalScale(new Vector3f(this.getWorldScale(), this.getWorldHeight(), this.getWorldScale()));
     
     // set the position of the new terrain, taking world scale into account
     Vector3f pos = this.fromTerrainLocation(location);
     tq.setLocalTranslation((pos.getX() + 1) * this.getWorldScale(), 0, (pos.getZ() + 1) * this.getWorldScale());
     tq.addControl(new RigidBodyControl(new HeightfieldCollisionShape(heightmap, tq.getLocalScale()), 0));
     
     return tq;
    

    }

    private float[] getHeightmap(int x, int z)
    {
    FloatBuffer buffer = this.filteredBasis.getBuffer(x * (this.getBlockSize() - 1), z * (this.getBlockSize() - 1), 0, this.getBlockSize());

     return buffer.array();
    

    }

    private FilteredBasis createNoiseGenerator()
    {
    FractalSum base = new FractalSum();
    base.setRoughness(0.7f);
    base.setFrequency(1.0f);
    base.setAmplitude(1.0f);
    base.setLacunarity(3.12f);
    base.setOctaves(8);
    base.setScale(0.02125f);
    base.addModulator(new NoiseModulator()
    {
    @Override
    public float value(float… in)
    {
    return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1);
    }
    });

     PerturbFilter perturb = new PerturbFilter();
     perturb.setMagnitude(0.119f);
     
     SmoothFilter smooth = new SmoothFilter();
     smooth.setRadius(1);
     smooth.setEffect(0.7f);
     
     OptimizedErode therm = new OptimizedErode();
     therm.setRadius(5);
     therm.setTalus(0.011f);
     
     IterativeFilter iterate = new IterativeFilter();
     iterate.addPreFilter(perturb);
     iterate.addPostFilter(smooth);
     iterate.setFilter(therm);
     iterate.setIterations(1);
     
     FilteredBasis ground = new FilteredBasis(base);
     ground.addPreFilter(iterate);
     
     return ground;
    

    }

    // version 1
    private Material createTerrainMaterial()
    {
    Material material = new Material(this.getApplication().getAssetManager(), “Common/MatDefs/Terrain/HeightBasedTerrain.j3md”);
    //Material material = new Material(this.getApplication().getAssetManager(), “Common/MatDefs/Misc/ShowNormals.j3md”);
    //material.getAdditionalRenderState().setWireframe(true);
    //material.setColor(“Color”, new ColorRGBA(255, 0, 0, 0));

     // GRASS texture
     Texture grass = this.getApplication().getAssetManager().loadTexture("Textures/Terrain/splat/grass.jpg");
     grass.setWrap(WrapMode.Repeat);
     grass.setMagFilter(MagFilter.Nearest);
     material.setTexture("region1ColorMap", grass);
     material.setVector3("region1", new Vector3f(88, 200, grassScale));
     //material.setVector3("region1", new Vector3f(0, 0, 16));
     
     // DIRT texture
     Texture dirt = this.getApplication().getAssetManager().loadTexture("Textures/Terrain/splat/dirt.jpg");
     dirt.setWrap(WrapMode.Repeat);
     dirt.setMagFilter(MagFilter.Nearest);
     material.setTexture("region2ColorMap", dirt);
     material.setVector3("region2", new Vector3f(0, 90, dirtScale));
     //material.setVector3("region2", new Vector3f(0, 0, 16));
     
     // ROCK texture
     Texture rock = this.getApplication().getAssetManager().loadTexture("Textures/Terrain/Rock/Rock.PNG");
     rock.setWrap(WrapMode.Repeat);
     rock.setMagFilter(MagFilter.Nearest);
     material.setTexture("region3ColorMap", rock);
     material.setVector3("region3", new Vector3f(198, 260, rockScale));
     //material.setVector3("region3", new Vector3f(0, 0, 16));
     
     material.setTexture("region4ColorMap", rock);
     material.setVector3("region4", new Vector3f(198, 260, rockScale));
     //material.setVector3("region4", new Vector3f(0, 0, 16));
     
     Texture rock2 = this.getApplication().getAssetManager().loadTexture("Textures/Terrain/Rock2/rock.jpg");
     rock2.setWrap(WrapMode.Repeat);
     rock2.setMagFilter(MagFilter.Nearest);
     material.setTexture("slopeColorMap", rock2);
     material.setFloat("slopeTileFactor", 32);
     
     material.setFloat("terrainSize", this.getBlockSize());
     
     return material;
    

    }
    }
    [/java]