Lots of javadoc

package game.test;

import com.jme.math.Vector3f;

import com.jme.renderer.ColorRGBA;

import com.jme.scene.Node;

import com.jme.scene.Spatial;

import com.jme.scene.shape.Box;

import com.jme.scene.state.MaterialState;

import com.jme.scene.state.RenderState;

import com.jme.system.DisplaySystem;

import java.util.Random;

/**

* A continuous floor for one player to wander about endlessly.

* <br />

* You provide the floor data in a square array of n*n fields. Each field

* contains one <em>field node</em> that has a <em>floor spatial</em>

* (and possibly other objects, trees, houses) attached to it.

* <br />

* In the update loop, you call wanderland.update() to keep 3*3 fields

* directly around the player.

* When the player reaches "the edge", Wanderland rolls over to the other

* side of the array, thus giving the appearance of an "endless" landscape.

* <br />

* <p><b>Floor Spatials:</b></p>

* <ul><li>can be a TerrainPage, Box, TerrainBlock, Quad, custom model, etc...

* </li><li>must be square shaped.

* </li><li>must have the same <tt>size</tt>.

* </li><li>The number of Floor Spatials must be a square number >= 3.

* <blockquote>If you use TerrainPages as floors and have stretched them,

* reflect that in the <tt>size</tt> value in the constructor:

* <tt>size = heightmap_size * stretch_factor</tt>.</blockquote>

* </li><li>Provide Floors Spatials with edges that match, otherwise expect holes.

* </li></ul>

* <p><b>Field Nodes:</b></p>

* <ul><li>

* Attach each Floor Spatial to one Field Node. Attach all trees,

* buildings, etc, that belong onto this floor to the same Field Node.

* </li><li>You have full accesss to your Field Nodes, and you can

* still modify them (attachments, State changes) during the game.

* </li><li>Do not attach your Field Nodes to anything; pass them into

* the wanderland constructor, and attach the wanderland object

* to (a node attached to) the rootNode.

* </li></ul>

*/

public class Wanderland extends Node {

/** Wanderland floor fields */

private final Node[][] data;

/** field length in world units */

private final int size;

/** This Wanderland is made up of n*n floor fields */

private final int n;

/** Loc of floor spatials can be in center or corner, needs adjustment */

private final Boolean fieldsAreCentered;

/** Remembering old value to determine whether player stepped into new field */

private Coord playerCoord_previous = new Coord(-1,-1);

private DisplaySystem display;

/**

* Creates a continuous floor for one player to wander about endlessly.

* Attach wanderland to rootNode, and use it together with update() in update loop.

* @param data A 2-D array of your floor field nodes.

* Array must be square, and must be larger or equal to 3*3.

* @param size Side length of one square floor field in world units.

* Must correspond to size of actual nodes that you provided,

* otherwise there will be overlaps or gaps.

* @param fieldsAreCentered Whether the provided floor fields' locs are

* centered (e.g. TerrainPage, Box => true), or

* in a corner (e.g. TerrainBlock, Quad => false).

*/

public Wanderland(Node[][] data, int size, Boolean fieldsAreCentered) {

this.data = data;

this.size = size;

this.fieldsAreCentered = fieldsAreCentered;

this.n = (int) data.length; // ignoring the remainder

}

/**

* DEMO mode. Creates a continuous floor for one player to wander about endlessly.

* This constructor generates a flat continuous floor filled with simple random objects.

* Attach this to rootNode, and use it together with update().

* @param number Number of floor fields to generate.

* Must be a square number >= 3*3. E.g. 9, 16, 25, 36...

* @param size Side length of floor fields to generate, in world units.

* @param display The main game's display system (to generate colors).

*/

public Wanderland(int number, int size, DisplaySystem display) {

this.size = size;

this.display = display;

this.fieldsAreCentered = true; // default for Box floor fields.

this.data = new Node[number][number];

this.n = (int) Math.sqrt(number); // ignoring the remainder

for (int row = 0; row < n; row++) {

for (int col = 0; col < n; col++) {

this.data[col][row] = new Node("DemoNode-" + col + "-" + row);

Box floor = new Box(

"demo-"+col+"-"+row , Vector3f.ZERO, size/2 , 0.1f , size/2 );

ColorRGBA gaudy = ColorRGBA.randomColor();

setColor(floor, gaudy);

this.data[col][row].attachChild(floor);

this.data[col][row].attachChild(createRandomContent(gaudy));

}

}

}

/**

* Call wanderland.update(cam.getLocation()) in your game's update() loop!

* @param playerloc3d The center of Wanderland in world units, e.g. cam.getLocation().

*/

public void update(Vector3f playerloc3d) {

// Multiplication factor for how far the player has wandered into new sectors.

Coord wander = new Coord(

(int) playerloc3d.x / (size * n) ,

(int) playerloc3d.z / (size * n));

// Make an adjustment to formula for negative numbers.

if( playerloc3d.x < 0 ) wander.x -= 1;

if( playerloc3d.z < 0 ) wander.z -= 1;

// playerCoord is an x/z coordinate within the data[z][x] array.

Coord playerCoord = new Coord(

Math.abs((Math.abs((int) playerloc3d.x) - (Math.abs(wander.x) * size * n)) / size) ,

Math.abs((Math.abs((int) playerloc3d.z) - (Math.abs(wander.z) * size * n)) / size) );

// If player has stepped over into new field, recenter neighbouring fields:

if ( !playerCoord_previous.equals(playerCoord) ) {

this.detachAllChildren();

for (int deltaX = -1; deltaX <= 1; deltaX++) {

for (int deltaZ = -1; deltaZ <= 1; deltaZ++) {

attachField( playerCoord, wander, deltaX, deltaZ );

}

}

}

playerCoord_previous = playerCoord; // remember previous value

this.updateGeometricState(0.1f, true);

this.updateRenderState();

}

/** Attach the 9 neighbouring fields around the player.

* @param playerCoord The player's coordinates within data[][].

* @param playerWander In which sector the player has wandered.

* @param deltaX Is it a neighbour to the left or right? -1|0|+1

* @param deltaZ Is it a neighbour in front or behind? -1|0|+1

* @return Side effect: The node is attached and translated.

*/

private void attachField( Coord playerCoord, Coord playerWander , int deltaX, int deltaZ ) {

Coord4 neighbour = rollover(playerCoord, playerWander, deltaX, deltaZ);

this.attachChild( data[neighbour.z][neighbour.x] );

float neighbourLocX = ( neighbour.x * size + (neighbour.wx * size * n) );

float neighbourLocZ = ( neighbour.z * size + (neighbour.wz * size * n) );

if( fieldsAreCentered ) {

neighbourLocX += size / 2;

neighbourLocZ += size / 2;

}

data[neighbour.z][neighbour.x].setLocalTranslation( neighbourLocX, 0, neighbourLocZ );

}

/**

* Determine whether this neighbour needs a roll-over.

* Give it the player coords and a delta (-1|0|+1),

* and it returns coords and sector of the respective neighbouring field.

* (Note: The playercoord is treated as a neighbour with delta 0|0.)

* @return Four integers: The x/z values of the neighbouring field

* inside data[][], and the x/z values of sector it is currently in.

*/

private Coord4 rollover( Coord playerCoord, Coord playerWander, int deltaX, int deltaZ) {

Coord neighbourCoord = new Coord( playerCoord.x, playerCoord.z );

Coord neighbourWander = new Coord( playerWander.x, playerWander.z );

neighbourCoord.x = playerCoord.x + deltaX;

neighbourCoord.z = playerCoord.z + deltaZ;

// coord can roll over to other side of data[n][n] block.

// wander can roll over into next sector.

if (neighbourCoord.x >= n) {

neighbourCoord.x -= n;

neighbourWander.x += 1;

} else if (neighbourCoord.x < 0) {

neighbourCoord.x += n;

neighbourWander.x -= 1;

}

if (neighbourCoord.z >= n) {

neighbourCoord.z -= n;

neighbourWander.z += 1;

} else if (neighbourCoord.z < 0) {

neighbourCoord.z += n;

neighbourWander.z -= 1;

}

return new Coord4(neighbourCoord, neighbourWander);

}

/** =============== Structs ============== **/

/** A struct that holds an int tupel, e.g. x/z coordinates. */

private class Coord {

public int x;

public int z;

public Coord(int x, int y) { this.x = x; this.z = y; }

@Override

public String toString() { return + x + "/" + z; }

@Override

public boolean equals(Object obj) {

if (obj instanceof Coord) {

Coord pt = (Coord)obj;

return (x == pt.x) && (z == pt.z);

}

return super.equals(obj);

}

@Override

public int hashCode() {

int hash = 7;

hash = 31 * hash + this.x;

hash = 31 * hash + this.z;

return hash;

}

}

/** A struct that holds an int quadrupel, e.g. x/z/wx/wz coordinates. */

private class Coord4 {

public int x;

public int z;

public int wx;

public int wz;

public Coord4(int x, int z, int wx, int wz)

{ this.x = x; this.z = z; this.wx = wx; this.wz = wz; }

public Coord4(Coord a, Coord b)

{ this.x = a.x; this.z = a.z; this.wx = b.x; this.wz = b.z; }

}

/** =============== Auxiliary methods for demos and testing ============== **/

/** Auxiliary coloring method. */

private void setColor(Spatial spatial, ColorRGBA color) {

final MaterialState materialState = display.getRenderer().createMaterialState();

materialState.setDiffuse(color);

spatial.setRenderState(materialState);

}

/** Auxiliary method, only used with the DEMO constructor.

* Create a few random box nodes to attach to the terrain

*/

private Node createRandomContent(ColorRGBA c) {

Node stuff = new Node("sample node");

Random r = new java.util.Random();

int max_num = r.nextInt(4) + 1;

for (int i = 0; i < max_num; i++) {

int scale1 = r.nextInt(size / 10) + 5;

int scale2 = r.nextInt(size / 10) + 5;

int scale3 = r.nextInt(size / 10) + 5;

int loc = r.nextInt(size / 3);

int pm1 = r.nextInt(2) == 1 ? 1 : -1;

int pm2 = r.nextInt(2) == 1 ? 1 : -1;

Box building = new Box("some stuff",

new Vector3f(loc * pm1, scale2, loc * pm2),

size / scale1, scale2, size / scale3);

setColor(building, c);

stuff.attachChild(building);

}

return stuff;

}

}