/ *
* 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 com.jme3.terrain.geomipmap ;
import com.jme3.bounding.BoundingBox ;
import com.jme3.bounding.BoundingVolume ;
import com.jme3.collision.Collidable ;
import com.jme3.collision.CollisionResults ;
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.Ray ;
import com.jme3.math.Vector2f ;
import com.jme3.math.Vector3f ;
import com.jme3.scene.Geometry ;
import com.jme3.scene.Node ;
import com.jme3.scene.Spatial ;
import com.jme3.scene.debug.WireBox ;
import com.jme3.terrain.ProgressMonitor ;
import com.jme3.terrain.Terrain ;
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator ;
import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker ;
import com.jme3.terrain.geomipmap.picking.TerrainPickData ;
import com.jme3.terrain.geomipmap.picking.TerrainPicker ;
import com.jme3.util.TangentBinormalGenerator ;
import com.jme3.util.clone.Cloner ;
import java.io.IOException ;
import java.util.ArrayList ;
import java.util.HashMap ;
import java.util.List ;
import java.util.Map ;
import java.util.logging.Level ;
import java.util.logging.Logger ;
/ * *
* < p >
* TerrainQuad is a heightfield - based terrain system . Heightfield terrain is fast and can
* render large areas , and allows for easy Level of Detail control . However it does not
* permit caves easily .
* TerrainQuad is a quad tree , meaning that the root quad has four children , and each of
* those children have four children . All the way down until you reach the bottom , the actual
* geometry , the TerrainPatches .
* If you look at a TerrainQuad in wireframe mode with the TerrainLODControl attached , you will
* see blocks that change their LOD level together ; these are the TerrainPatches . The TerrainQuad
* is just an organizational structure for the TerrainPatches so patches that are not in the
* view frustum get culled quickly .
* TerrainQuads size are a power of 2 , plus 1 . So 513x513 , or 1025x1025 etc .
* Each point in the terrain is one unit apart from its neighbour . So a 513x513 terrain
* will be 513 units wide and 513 units long .
* Patch size can be specified on the terrain . This sets how large each geometry ( TerrainPatch )
* is . It also must be a power of 2 plus 1 so the terrain can be subdivided equally .
* < / p >
* < p >
* The height of the terrain can be modified at runtime using setHeight ( )
* < / p >
* < p >
* A terrain quad is a node in the quad tree of the terrain system .
* The root terrain quad will be the only one that receives the update ( ) call every frame
* and it will determine if there has been any LOD change .
* < / p > < p >
* The leaves of the terrain quad tree are Terrain Patches . These have the real geometry mesh .
* < / p > < p >
* Heightmap coordinates start from the bottom left of the world and work towards the
* top right .
* < / p > < pre >
* + x
* ^
* | . . . . . . N = length of heightmap
* | : :
* | : :
* | 0 . . . . . :
* + - - - - - - - - - & gt ; + z
* ( world coordinates )
* < / pre >
* @author Brent Owens
* /
public class TerrainQuad extends Node implements Terrain {
protected Vector2f offset ;
protected int totalSize ; // the size of this entire terrain tree (on one side)
protected int size ; // size of this quad, can be between totalSize and patchSize
protected int patchSize ; // size of the individual patches
protected Vector3f stepScale ;
protected float offsetAmount ;
protected int quadrant = 0 ; // 1=upper left, 2=lower left, 3=upper right, 4=lower right
private int maxLod = - 1 ;
private BoundingBox affectedAreaBBox ; // only set in the root quad
private TerrainPicker picker ;
private Vector3f lastScale = Vector3f . UNIT_XYZ ;
protected NeighbourFinder neighbourFinder ;
public TerrainQuad ( ) {
super ( "Terrain" ) ;
}
/ * *
* Creates a terrain with :
* < ul >
* < li > the total , real - world , size of the terrain < / li >
* < li > the patchSize , or the size of each geometry tile of the terrain < / li >
* < li > the heightmap that defines the height of the terrain < / li >
* < / ul >
* < p >
* A TerrainQuad of totalSize 513x513 will be 513 units wide and 513 units long .
* PatchSize is just used to subdivide the terrain into tiles that can be culled .
* < / p >
* @param name the name of the scene element . This is required for
* identification and comparison purposes .
* @param patchSize size of the individual patches ( geometry ) . Power of 2 plus 1 ,
* must be smaller than totalSize . ( eg . 33 , 65 . . . )
* @param totalSize the size of this entire terrain ( on one side ) . Power of 2 plus 1
* ( eg . 513 , 1025 , 2049 . . . )
* @param heightMap The height map to generate the terrain from ( a flat
* height map will be generated if this is null ) . The size of one side of the heightmap
* must match the totalSize . So a 513x513 heightmap is needed for a terrain with totalSize of 513 .
* /
public TerrainQuad ( String name , int patchSize , int totalSize , float [ ] heightMap ) {
this ( name , patchSize , totalSize , Vector3f . UNIT_XYZ , heightMap ) ;
affectedAreaBBox = new BoundingBox ( new Vector3f ( 0 , 0 , 0 ) , size * 2 , Float . MAX_VALUE , size * 2 ) ;
fixNormalEdges ( affectedAreaBBox ) ;
addControl ( new NormalRecalcControl ( this ) ) ;
}
/ * *
*
* @param name the name of the scene element . This is required for
* identification and comparison purposes .
* @param patchSize size of the individual patches
* @param size size of this quad , can be between totalSize and patchSize
* @param scale
* @param heightMap The height map to generate the terrain from ( a flat
* height map will be generated if this is null )
* /
@Deprecated
public TerrainQuad ( String name , int patchSize , int size , Vector3f scale , float [ ] heightMap ) {
this ( name , patchSize , size , scale , heightMap , size , new Vector2f ( ) , 0 ) ;
//affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);
//fixNormalEdges(affectedAreaBBox);
//addControl(new NormalRecalcControl(this));
}
/ * *
*
* @param name the name of the scene element . This is required for
* identification and comparison purposes .
* @param patchSize size of the individual patches
* @param totalSize the size of this entire terrain tree ( on one side )
* @param quadSize
* @param scale
* @param heightMap The height map to generate the terrain from ( a flat
* height map will be generated if this is null )
* /
@Deprecated
public TerrainQuad ( String name , int patchSize , int totalSize , int quadSize , Vector3f scale , float [ ] heightMap ) {
this ( name , patchSize , quadSize , scale , heightMap , totalSize , new Vector2f ( ) , 0 ) ;
//affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2);
//fixNormalEdges(affectedAreaBBox);
//addControl(new NormalRecalcControl(this));
}
protected TerrainQuad ( String name , int patchSize , int quadSize ,
Vector3f scale , float [ ] heightMap , int totalSize ,
Vector2f offset , float offsetAmount )
{
super ( name ) ;
if ( heightMap = = null )
heightMap = generateDefaultHeightMap ( quadSize ) ;
if ( ! FastMath . isPowerOfTwo ( quadSize - 1 ) ) {
throw new RuntimeException ( "size given: " + quadSize + " Terrain quad sizes may only be (2^N + 1)" ) ;
}
if ( FastMath . sqrt ( heightMap . length ) > quadSize ) {
Logger . getLogger ( this . getClass ( ) . getName ( ) ) . log ( Level . WARNING , "Heightmap size is larger than the terrain size. Make sure your heightmap image is the same size as the terrain!" ) ;
}
this . offset = offset ;
this . offsetAmount = offsetAmount ;
this . totalSize = totalSize ;
this . size = quadSize ;
this . patchSize = patchSize ;
this . stepScale = scale ;
split ( patchSize , heightMap ) ;
}
public void setNeighbourFinder ( NeighbourFinder neighbourFinder ) {
this . neighbourFinder = neighbourFinder ;
resetCachedNeighbours ( ) ;
}
/ * *
* Forces the recalculation of all normals on the terrain .
* /
public void recalculateAllNormals ( ) {
affectedAreaBBox = new BoundingBox ( new Vector3f ( 0 , 0 , 0 ) , totalSize * 2 , Float . MAX_VALUE , totalSize * 2 ) ;
}
/ * *
* Create just a flat heightmap
* /
private float [ ] generateDefaultHeightMap ( int size ) {
float [ ] heightMap = new float [ size * size ] ;
return heightMap ;
}
/ * *
* update the normals if there were any height changes recently .
* Should only be called on the root quad
* /
protected void updateNormals ( ) {
if ( needToRecalculateNormals ( ) ) {
//TODO background-thread this if it ends up being expensive
fixNormals ( affectedAreaBBox ) ; // the affected patches
fixNormalEdges ( affectedAreaBBox ) ; // the edges between the patches
setNormalRecalcNeeded ( null ) ; // set to false
}
}
/ * *
* Caches the transforms ( except rotation ) so the LOD calculator ,
* which runs on a separate thread , can access them safely .
* /
protected void cacheTerrainTransforms ( ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial child = children . get ( i ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . cacheTerrainTransforms ( ) ;
} else if ( child instanceof TerrainPatch ) {
( ( TerrainPatch ) child ) . cacheTerrainTransforms ( ) ;
}
}
}
private int collideWithRay ( Ray ray , CollisionResults results ) {
if ( picker = = null )
picker = new BresenhamTerrainPicker ( this ) ;
Vector3f intersection = picker . getTerrainIntersection ( ray , results ) ;
if ( intersection ! = null ) {
if ( ray . getLimit ( ) < Float . POSITIVE_INFINITY ) {
if ( results . getClosestCollision ( ) . getDistance ( ) < = ray . getLimit ( ) )
return 1 ; // in range
else
return 0 ; // out of range
} else
return 1 ;
} else
return 0 ;
}
/ * *
* Generate the entropy values for the terrain for the "perspective" LOD
* calculator . This routine can take a long time to run !
* @param progressMonitor optional
* /
public void generateEntropy ( ProgressMonitor progressMonitor ) {
// only check this on the root quad
if ( isRootQuad ( ) )
if ( progressMonitor ! = null ) {
int numCalc = ( totalSize - 1 ) / ( patchSize - 1 ) ; // make it an even number
progressMonitor . setMonitorMax ( numCalc * numCalc ) ;
}
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial child = children . get ( i ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . generateEntropy ( progressMonitor ) ;
} else if ( child instanceof TerrainPatch ) {
( ( TerrainPatch ) child ) . generateLodEntropies ( ) ;
if ( progressMonitor ! = null )
progressMonitor . incrementProgress ( 1 ) ;
}
}
}
// only do this on the root quad
if ( isRootQuad ( ) )
if ( progressMonitor ! = null )
progressMonitor . progressComplete ( ) ;
}
protected boolean isRootQuad ( ) {
return ( getParent ( ) ! = null & & ! ( getParent ( ) instanceof TerrainQuad ) ) ;
}
public Material getMaterial ( ) {
return getMaterial ( null ) ;
}
public Material getMaterial ( Vector3f worldLocation ) {
// get the material from one of the children. They all share the same material
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial child = children . get ( i ) ;
if ( child instanceof TerrainQuad ) {
return ( ( TerrainQuad ) child ) . getMaterial ( worldLocation ) ;
} else if ( child instanceof TerrainPatch ) {
return ( ( TerrainPatch ) child ) . getMaterial ( ) ;
}
}
}
return null ;
}
public int getNumMajorSubdivisions ( ) {
return 1 ;
}
protected boolean calculateLod ( List < Vector3f > location , HashMap < String , UpdatedTerrainPatch > updates , LodCalculator lodCalculator ) {
boolean lodChanged = false ;
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial child = children . get ( i ) ;
if ( child instanceof TerrainQuad ) {
boolean b = ( ( TerrainQuad ) child ) . calculateLod ( location , updates , lodCalculator ) ;
if ( b )
lodChanged = true ;
} else if ( child instanceof TerrainPatch ) {
boolean b = lodCalculator . calculateLod ( ( TerrainPatch ) child , location , updates ) ;
if ( b )
lodChanged = true ;
}
}
}
return lodChanged ;
}
protected synchronized void findNeighboursLod ( HashMap < String , UpdatedTerrainPatch > updated ) {
if ( children ! = null ) {
for ( int x = children . size ( ) ; - - x > = 0 ; ) {
Spatial child = children . get ( x ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . findNeighboursLod ( updated ) ;
} else if ( child instanceof TerrainPatch ) {
TerrainPatch patch = ( TerrainPatch ) child ;
if ( ! patch . searchedForNeighboursAlready ) {
// set the references to the neighbours
patch . rightNeighbour = findRightPatch ( patch ) ;
patch . bottomNeighbour = findDownPatch ( patch ) ;
patch . leftNeighbour = findLeftPatch ( patch ) ;
patch . topNeighbour = findTopPatch ( patch ) ;
patch . searchedForNeighboursAlready = true ;
}
TerrainPatch right = patch . rightNeighbour ;
TerrainPatch down = patch . bottomNeighbour ;
TerrainPatch left = patch . leftNeighbour ;
TerrainPatch top = patch . topNeighbour ;
UpdatedTerrainPatch utp = updated . get ( patch . getName ( ) ) ;
if ( utp = = null ) {
utp = new UpdatedTerrainPatch ( patch , patch . lod ) ;
updated . put ( utp . getName ( ) , utp ) ;
}
if ( right ! = null ) {
UpdatedTerrainPatch utpR = updated . get ( right . getName ( ) ) ;
if ( utpR = = null ) {
utpR = new UpdatedTerrainPatch ( right ) ;
updated . put ( utpR . getName ( ) , utpR ) ;
utpR . setNewLod ( right . lod ) ;
}
utp . setRightLod ( utpR . getNewLod ( ) ) ;
utpR . setLeftLod ( utp . getNewLod ( ) ) ;
}
if ( down ! = null ) {
UpdatedTerrainPatch utpD = updated . get ( down . getName ( ) ) ;
if ( utpD = = null ) {
utpD = new UpdatedTerrainPatch ( down ) ;
updated . put ( utpD . getName ( ) , utpD ) ;
utpD . setNewLod ( down . lod ) ;
}
utp . setBottomLod ( utpD . getNewLod ( ) ) ;
utpD . setTopLod ( utp . getNewLod ( ) ) ;
}
if ( left ! = null ) {
UpdatedTerrainPatch utpL = updated . get ( left . getName ( ) ) ;
if ( utpL = = null ) {
utpL = new UpdatedTerrainPatch ( left ) ;
updated . put ( utpL . getName ( ) , utpL ) ;
utpL . setNewLod ( left . lod ) ;
}
utp . setLeftLod ( utpL . getNewLod ( ) ) ;
utpL . setRightLod ( utp . getNewLod ( ) ) ;
}
if ( top ! = null ) {
UpdatedTerrainPatch utpT = updated . get ( top . getName ( ) ) ;
if ( utpT = = null ) {
utpT = new UpdatedTerrainPatch ( top ) ;
updated . put ( utpT . getName ( ) , utpT ) ;
utpT . setNewLod ( top . lod ) ;
}
utp . setTopLod ( utpT . getNewLod ( ) ) ;
utpT . setBottomLod ( utp . getNewLod ( ) ) ;
}
}
}
}
}
/ * *
* Reset the cached references of neighbours .
* TerrainQuad caches neighbours for faster LOD checks .
* Sometimes you might want to reset this cache ( for instance in TerrainGrid )
* /
public void resetCachedNeighbours ( ) {
if ( children ! = null ) {
for ( int x = children . size ( ) ; - - x > = 0 ; ) {
Spatial child = children . get ( x ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . resetCachedNeighbours ( ) ;
} else if ( child instanceof TerrainPatch ) {
TerrainPatch patch = ( TerrainPatch ) child ;
patch . searchedForNeighboursAlready = false ;
}
}
}
}
/ * *
* Find any neighbours that should have their edges seamed because another neighbour
* changed its LOD to a greater value ( less detailed )
* /
protected synchronized void fixEdges ( HashMap < String , UpdatedTerrainPatch > updated ) {
if ( children ! = null ) {
for ( int x = children . size ( ) ; - - x > = 0 ; ) {
Spatial child = children . get ( x ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . fixEdges ( updated ) ;
} else if ( child instanceof TerrainPatch ) {
TerrainPatch patch = ( TerrainPatch ) child ;
UpdatedTerrainPatch utp = updated . get ( patch . getName ( ) ) ;
if ( utp ! = null & & utp . lodChanged ( ) ) {
if ( ! patch . searchedForNeighboursAlready ) {
// set the references to the neighbours
patch . rightNeighbour = findRightPatch ( patch ) ;
patch . bottomNeighbour = findDownPatch ( patch ) ;
patch . leftNeighbour = findLeftPatch ( patch ) ;
patch . topNeighbour = findTopPatch ( patch ) ;
patch . searchedForNeighboursAlready = true ;
}
TerrainPatch right = patch . rightNeighbour ;
TerrainPatch down = patch . bottomNeighbour ;
TerrainPatch top = patch . topNeighbour ;
TerrainPatch left = patch . leftNeighbour ;
if ( right ! = null ) {
UpdatedTerrainPatch utpR = updated . get ( right . getName ( ) ) ;
if ( utpR = = null ) {
utpR = new UpdatedTerrainPatch ( right ) ;
updated . put ( utpR . getName ( ) , utpR ) ;
utpR . setNewLod ( right . lod ) ;
}
utpR . setLeftLod ( utp . getNewLod ( ) ) ;
utpR . setFixEdges ( true ) ;
}
if ( down ! = null ) {
UpdatedTerrainPatch utpD = updated . get ( down . getName ( ) ) ;
if ( utpD = = null ) {
utpD = new UpdatedTerrainPatch ( down ) ;
updated . put ( utpD . getName ( ) , utpD ) ;
utpD . setNewLod ( down . lod ) ;
}
utpD . setTopLod ( utp . getNewLod ( ) ) ;
utpD . setFixEdges ( true ) ;
}
if ( top ! = null ) {
UpdatedTerrainPatch utpT = updated . get ( top . getName ( ) ) ;
if ( utpT = = null ) {
utpT = new UpdatedTerrainPatch ( top ) ;
updated . put ( utpT . getName ( ) , utpT ) ;
utpT . setNewLod ( top . lod ) ;
}
utpT . setBottomLod ( utp . getNewLod ( ) ) ;
utpT . setFixEdges ( true ) ;
}
if ( left ! = null ) {
UpdatedTerrainPatch utpL = updated . get ( left . getName ( ) ) ;
if ( utpL = = null ) {
utpL = new UpdatedTerrainPatch ( left ) ;
updated . put ( utpL . getName ( ) , utpL ) ;
utpL . setNewLod ( left . lod ) ;
}
utpL . setRightLod ( utp . getNewLod ( ) ) ;
utpL . setFixEdges ( true ) ;
}
}
}
}
}
}
protected synchronized void reIndexPages ( HashMap < String , UpdatedTerrainPatch > updated , boolean usesVariableLod ) {
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial child = children . get ( i ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . reIndexPages ( updated , usesVariableLod ) ;
} else if ( child instanceof TerrainPatch ) {
( ( TerrainPatch ) child ) . reIndexGeometry ( updated , usesVariableLod ) ;
}
}
}
}
/ * *
* < code > split < / code > divides the heightmap data for four children . The
* children are either quads or patches . This is dependent on the size of the
* children . If the child ' s size is less than or equal to the set block
* size , then patches are created , otherwise , quads are created .
*
* @param blockSize
* the blocks size to test against .
* @param heightMap
* the height data .
* /
protected void split ( int blockSize , float [ ] heightMap ) {
if ( ( size > > 1 ) + 1 < = blockSize ) {
createQuadPatch ( heightMap ) ;
} else {
createQuad ( blockSize , heightMap ) ;
}
}
/ * *
* Quadrants , world coordinates , and heightmap coordinates ( Y - up ) :
*
* - z
* - u |
* - v 1 | 3
* - x - - - - + - - - - x
* 2 | 4 u
* | v
* z
* < code > createQuad < / code > generates four new quads from this quad .
* The heightmap ' s top left ( 0 , 0 ) coordinate is at the bottom , - x , - z
* coordinate of the terrain , so it grows in the positive x . z direction .
* /
protected void createQuad ( int blockSize , float [ ] heightMap ) {
// create 4 terrain quads
int quarterSize = size > > 2 ;
int split = ( size + 1 ) > > 1 ;
Vector2f tempOffset = new Vector2f ( ) ;
offsetAmount + = quarterSize ;
//if (lodCalculator == null)
// lodCalculator = createDefaultLodCalculator(); // set a default one
// 1 upper left of heightmap, upper left quad
float [ ] heightBlock1 = createHeightSubBlock ( heightMap , 0 , 0 , split ) ;
Vector3f origin1 = new Vector3f ( - quarterSize * stepScale . x , 0 ,
- quarterSize * stepScale . z ) ;
tempOffset . x = offset . x ;
tempOffset . y = offset . y ;
tempOffset . x + = origin1 . x ;
tempOffset . y + = origin1 . z ;
TerrainQuad quad1 = new TerrainQuad ( getName ( ) + "Quad1" , blockSize ,
split , stepScale , heightBlock1 , totalSize , tempOffset ,
offsetAmount ) ;
quad1 . setLocalTranslation ( origin1 ) ;
quad1 . quadrant = 1 ;
this . attachChild ( quad1 ) ;
// 2 lower left of heightmap, lower left quad
float [ ] heightBlock2 = createHeightSubBlock ( heightMap , 0 , split - 1 ,
split ) ;
Vector3f origin2 = new Vector3f ( - quarterSize * stepScale . x , 0 ,
quarterSize * stepScale . z ) ;
tempOffset = new Vector2f ( ) ;
tempOffset . x = offset . x ;
tempOffset . y = offset . y ;
tempOffset . x + = origin2 . x ;
tempOffset . y + = origin2 . z ;
TerrainQuad quad2 = new TerrainQuad ( getName ( ) + "Quad2" , blockSize ,
split , stepScale , heightBlock2 , totalSize , tempOffset ,
offsetAmount ) ;
quad2 . setLocalTranslation ( origin2 ) ;
quad2 . quadrant = 2 ;
this . attachChild ( quad2 ) ;
// 3 upper right of heightmap, upper right quad
float [ ] heightBlock3 = createHeightSubBlock ( heightMap , split - 1 , 0 ,
split ) ;
Vector3f origin3 = new Vector3f ( quarterSize * stepScale . x , 0 ,
- quarterSize * stepScale . z ) ;
tempOffset = new Vector2f ( ) ;
tempOffset . x = offset . x ;
tempOffset . y = offset . y ;
tempOffset . x + = origin3 . x ;
tempOffset . y + = origin3 . z ;
TerrainQuad quad3 = new TerrainQuad ( getName ( ) + "Quad3" , blockSize ,
split , stepScale , heightBlock3 , totalSize , tempOffset ,
offsetAmount ) ;
quad3 . setLocalTranslation ( origin3 ) ;
quad3 . quadrant = 3 ;
this . attachChild ( quad3 ) ;
// 4 lower right of heightmap, lower right quad
float [ ] heightBlock4 = createHeightSubBlock ( heightMap , split - 1 ,
split - 1 , split ) ;
Vector3f origin4 = new Vector3f ( quarterSize * stepScale . x , 0 ,
quarterSize * stepScale . z ) ;
tempOffset = new Vector2f ( ) ;
tempOffset . x = offset . x ;
tempOffset . y = offset . y ;
tempOffset . x + = origin4 . x ;
tempOffset . y + = origin4 . z ;
TerrainQuad quad4 = new TerrainQuad ( getName ( ) + "Quad4" , blockSize ,
split , stepScale , heightBlock4 , totalSize , tempOffset ,
offsetAmount ) ;
quad4 . setLocalTranslation ( origin4 ) ;
quad4 . quadrant = 4 ;
this . attachChild ( quad4 ) ;
}
public void generateDebugTangents ( Material mat ) {
for ( int x = children . size ( ) ; - - x > = 0 ; ) {
Spatial child = children . get ( x ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . generateDebugTangents ( mat ) ;
} else if ( child instanceof TerrainPatch ) {
Geometry debug = new Geometry ( "Debug " + name ,
TangentBinormalGenerator . genTbnLines ( ( ( TerrainPatch ) child ) . getMesh ( ) , 0 . 8f ) ) ;
attachChild ( debug ) ;
debug . setLocalTranslation ( child . getLocalTranslation ( ) ) ;
debug . setCullHint ( CullHint . Never ) ;
debug . setMaterial ( mat ) ;
}
}
}
/ * *
* < code > createQuadPatch < / code > creates four child patches from this quad .
* /
protected void createQuadPatch ( float [ ] heightMap ) {
// create 4 terrain patches
int quarterSize = size > > 2 ;
int halfSize = size > > 1 ;
int split = ( size + 1 ) > > 1 ;
//if (lodCalculator == null)
// lodCalculator = createDefaultLodCalculator(); // set a default one
offsetAmount + = quarterSize ;
// 1 lower left
float [ ] heightBlock1 = createHeightSubBlock ( heightMap , 0 , 0 , split ) ;
Vector3f origin1 = new Vector3f ( - halfSize * stepScale . x , 0 , - halfSize
* stepScale . z ) ;
Vector2f tempOffset1 = new Vector2f ( ) ;
tempOffset1 . x = offset . x ;
tempOffset1 . y = offset . y ;
tempOffset1 . x + = origin1 . x / 2 ;
tempOffset1 . y + = origin1 . z / 2 ;
TerrainPatch patch1 = new TerrainPatch ( getName ( ) + "Patch1" , split ,
stepScale , heightBlock1 , origin1 , totalSize , tempOffset1 ,
offsetAmount ) ;
patch1 . setQuadrant ( ( short ) 1 ) ;
this . attachChild ( patch1 ) ;
patch1 . setModelBound ( new BoundingBox ( ) ) ;
patch1 . updateModelBound ( ) ;
//patch1.setLodCalculator(lodCalculator);
//TangentBinormalGenerator.generate(patch1);
// 2 upper left
float [ ] heightBlock2 = createHeightSubBlock ( heightMap , 0 , split - 1 ,
split ) ;
Vector3f origin2 = new Vector3f ( - halfSize * stepScale . x , 0 , 0 ) ;
Vector2f tempOffset2 = new Vector2f ( ) ;
tempOffset2 . x = offset . x ;
tempOffset2 . y = offset . y ;
tempOffset2 . x + = origin1 . x / 2 ;
tempOffset2 . y + = quarterSize * stepScale . z ;
TerrainPatch patch2 = new TerrainPatch ( getName ( ) + "Patch2" , split ,
stepScale , heightBlock2 , origin2 , totalSize , tempOffset2 ,
offsetAmount ) ;
patch2 . setQuadrant ( ( short ) 2 ) ;
this . attachChild ( patch2 ) ;
patch2 . setModelBound ( new BoundingBox ( ) ) ;
patch2 . updateModelBound ( ) ;
//patch2.setLodCalculator(lodCalculator);
//TangentBinormalGenerator.generate(patch2);
// 3 lower right
float [ ] heightBlock3 = createHeightSubBlock ( heightMap , split - 1 , 0 ,
split ) ;
Vector3f origin3 = new Vector3f ( 0 , 0 , - halfSize * stepScale . z ) ;
Vector2f tempOffset3 = new Vector2f ( ) ;
tempOffset3 . x = offset . x ;
tempOffset3 . y = offset . y ;
tempOffset3 . x + = quarterSize * stepScale . x ;
tempOffset3 . y + = origin3 . z / 2 ;
TerrainPatch patch3 = new TerrainPatch ( getName ( ) + "Patch3" , split ,
stepScale , heightBlock3 , origin3 , totalSize , tempOffset3 ,
offsetAmount ) ;
patch3 . setQuadrant ( ( short ) 3 ) ;
this . attachChild ( patch3 ) ;
patch3 . setModelBound ( new BoundingBox ( ) ) ;
patch3 . updateModelBound ( ) ;
//patch3.setLodCalculator(lodCalculator);
//TangentBinormalGenerator.generate(patch3);
// 4 upper right
float [ ] heightBlock4 = createHeightSubBlock ( heightMap , split - 1 ,
split - 1 , split ) ;
Vector3f origin4 = new Vector3f ( 0 , 0 , 0 ) ;
Vector2f tempOffset4 = new Vector2f ( ) ;
tempOffset4 . x = offset . x ;
tempOffset4 . y = offset . y ;
tempOffset4 . x + = quarterSize * stepScale . x ;
tempOffset4 . y + = quarterSize * stepScale . z ;
TerrainPatch patch4 = new TerrainPatch ( getName ( ) + "Patch4" , split ,
stepScale , heightBlock4 , origin4 , totalSize , tempOffset4 ,
offsetAmount ) ;
patch4 . setQuadrant ( ( short ) 4 ) ;
this . attachChild ( patch4 ) ;
patch4 . setModelBound ( new BoundingBox ( ) ) ;
patch4 . updateModelBound ( ) ;
//patch4.setLodCalculator(lodCalculator);
//TangentBinormalGenerator.generate(patch4);
}
public float [ ] createHeightSubBlock ( float [ ] heightMap , int x ,
int y , int side ) {
float [ ] rVal = new float [ side * side ] ;
int bsize = ( int ) FastMath . sqrt ( heightMap . length ) ;
int count = 0 ;
for ( int i = y ; i < side + y ; i + + ) {
for ( int j = x ; j < side + x ; j + + ) {
if ( j < bsize & & i < bsize )
rVal [ count ] = heightMap [ j + ( i * bsize ) ] ;
count + + ;
}
}
return rVal ;
}
/ * *
* A handy method that will attach all bounding boxes of this terrain
* to the node you supply .
* Useful to visualize the bounding boxes when debugging .
*
* @param parent that will get the bounding box shapes of the terrain attached to
* /
public void attachBoundChildren ( Node parent ) {
for ( int i = 0 ; i < this . getQuantity ( ) ; i + + ) {
if ( this . getChild ( i ) instanceof TerrainQuad ) {
( ( TerrainQuad ) getChild ( i ) ) . attachBoundChildren ( parent ) ;
} else if ( this . getChild ( i ) instanceof TerrainPatch ) {
BoundingVolume bv = getChild ( i ) . getWorldBound ( ) ;
if ( bv instanceof BoundingBox ) {
attachBoundingBox ( ( BoundingBox ) bv , parent ) ;
}
}
}
BoundingVolume bv = getWorldBound ( ) ;
if ( bv instanceof BoundingBox ) {
attachBoundingBox ( ( BoundingBox ) bv , parent ) ;
}
}
/ * *
* used by attachBoundChildren ( )
* /
private void attachBoundingBox ( BoundingBox bb , Node parent ) {
WireBox wb = new WireBox ( bb . getXExtent ( ) , bb . getYExtent ( ) , bb . getZExtent ( ) ) ;
Geometry g = new Geometry ( ) ;
g . setMesh ( wb ) ;
g . setLocalTranslation ( bb . getCenter ( ) ) ;
parent . attachChild ( g ) ;
}
/ * *
* Signal if the normal vectors for the terrain need to be recalculated .
* Does this by looking at the affectedAreaBBox bounding box . If the bbox
* exists already , then it will grow the box to fit the new changedPoint .
* If the affectedAreaBBox is null , then it will create one of unit size .
*
* @param needToRecalculateNormals if null , will cause needToRecalculateNormals ( ) to return false
* /
protected void setNormalRecalcNeeded ( Vector2f changedPoint ) {
if ( changedPoint = = null ) { // set needToRecalculateNormals() to false
affectedAreaBBox = null ;
return ;
}
if ( affectedAreaBBox = = null ) {
affectedAreaBBox = new BoundingBox ( new Vector3f ( changedPoint . x , 0 , changedPoint . y ) , 1f , Float . MAX_VALUE , 1f ) ; // unit length
} else {
// adjust size of box to be larger
affectedAreaBBox . mergeLocal ( new BoundingBox ( new Vector3f ( changedPoint . x , 0 , changedPoint . y ) , 1f , Float . MAX_VALUE , 1f ) ) ;
}
}
protected boolean needToRecalculateNormals ( ) {
if ( affectedAreaBBox ! = null )
return true ;
if ( ! lastScale . equals ( getWorldScale ( ) ) ) {
affectedAreaBBox = new BoundingBox ( getWorldTranslation ( ) , Float . MAX_VALUE , Float . MAX_VALUE , Float . MAX_VALUE ) ;
lastScale = getWorldScale ( ) ;
return true ;
}
return false ;
}
/ * *
* This will cause all normals for this terrain quad to be recalculated
* /
protected void setNeedToRecalculateNormals ( ) {
affectedAreaBBox = new BoundingBox ( getWorldTranslation ( ) , Float . MAX_VALUE , Float . MAX_VALUE , Float . MAX_VALUE ) ;
}
public float getHeightmapHeight ( Vector2f xz ) {
// offset
int halfSize = totalSize / 2 ;
int x = Math . round ( ( xz . x / getWorldScale ( ) . x ) + halfSize ) ;
int z = Math . round ( ( xz . y / getWorldScale ( ) . z ) + halfSize ) ;
if ( ! isInside ( x , z ) )
return Float . NaN ;
return getHeightmapHeight ( x , z ) ;
}
/ * *
* This will just get the heightmap value at the supplied point ,
* not an interpolated ( actual ) height value .
* /
protected float getHeightmapHeight ( int x , int z ) {
int quad = findQuadrant ( x , z ) ;
int split = ( size + 1 ) > > 1 ;
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial spat = children . get ( i ) ;
int col = x ;
int row = z ;
boolean match = false ;
// get the childs quadrant
int childQuadrant = 0 ;
if ( spat instanceof TerrainQuad ) {
childQuadrant = ( ( TerrainQuad ) spat ) . getQuadrant ( ) ;
} else if ( spat instanceof TerrainPatch ) {
childQuadrant = ( ( TerrainPatch ) spat ) . getQuadrant ( ) ;
}
if ( childQuadrant = = 1 & & ( quad & 1 ) ! = 0 ) {
match = true ;
} else if ( childQuadrant = = 2 & & ( quad & 2 ) ! = 0 ) {
row = z - split + 1 ;
match = true ;
} else if ( childQuadrant = = 3 & & ( quad & 4 ) ! = 0 ) {
col = x - split + 1 ;
match = true ;
} else if ( childQuadrant = = 4 & & ( quad & 8 ) ! = 0 ) {
col = x - split + 1 ;
row = z - split + 1 ;
match = true ;
}
if ( match ) {
if ( spat instanceof TerrainQuad ) {
return ( ( TerrainQuad ) spat ) . getHeightmapHeight ( col , row ) ;
} else if ( spat instanceof TerrainPatch ) {
return ( ( TerrainPatch ) spat ) . getHeightmapHeight ( col , row ) ;
}
}
}
}
return Float . NaN ;
}
protected Vector3f getMeshNormal ( int x , int z ) {
int quad = findQuadrant ( x , z ) ;
int split = ( size + 1 ) > > 1 ;
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial spat = children . get ( i ) ;
int col = x ;
int row = z ;
boolean match = false ;
// get the childs quadrant
int childQuadrant = 0 ;
if ( spat instanceof TerrainQuad ) {
childQuadrant = ( ( TerrainQuad ) spat ) . getQuadrant ( ) ;
} else if ( spat instanceof TerrainPatch ) {
childQuadrant = ( ( TerrainPatch ) spat ) . getQuadrant ( ) ;
}
if ( childQuadrant = = 1 & & ( quad & 1 ) ! = 0 ) {
match = true ;
} else if ( childQuadrant = = 2 & & ( quad & 2 ) ! = 0 ) {
row = z - split + 1 ;
match = true ;
} else if ( childQuadrant = = 3 & & ( quad & 4 ) ! = 0 ) {
col = x - split + 1 ;
match = true ;
} else if ( childQuadrant = = 4 & & ( quad & 8 ) ! = 0 ) {
col = x - split + 1 ;
row = z - split + 1 ;
match = true ;
}
if ( match ) {
if ( spat instanceof TerrainQuad ) {
return ( ( TerrainQuad ) spat ) . getMeshNormal ( col , row ) ;
} else if ( spat instanceof TerrainPatch ) {
return ( ( TerrainPatch ) spat ) . getMeshNormal ( col , row ) ;
}
}
}
}
return null ;
}
/ * *
* is the 2d point inside the terrain ?
* @param x local coordinate
* @param z local coordinate
* /
private boolean isInside ( int x , int z ) {
if ( x < 0 | | z < 0 | | x > totalSize | | z > totalSize )
return false ;
return true ;
}
/ * *
* Used for searching for a child and keeping
* track of its quadrant
* /
private class QuadrantChild {
int col ;
int row ;
Spatial child ;
QuadrantChild ( int col , int row , Spatial child ) {
this . col = col ;
this . row = row ;
this . child = child ;
}
}
private QuadrantChild findMatchingChild ( int x , int z ) {
int quad = findQuadrant ( x , z ) ;
int split = ( size + 1 ) > > 1 ;
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial spat = children . get ( i ) ;
int col = x ;
int row = z ;
boolean match = false ;
// get the childs quadrant
int childQuadrant = 0 ;
if ( spat instanceof TerrainQuad ) {
childQuadrant = ( ( TerrainQuad ) spat ) . getQuadrant ( ) ;
} else if ( spat instanceof TerrainPatch ) {
childQuadrant = ( ( TerrainPatch ) spat ) . getQuadrant ( ) ;
}
if ( childQuadrant = = 1 & & ( quad & 1 ) ! = 0 ) {
match = true ;
} else if ( childQuadrant = = 2 & & ( quad & 2 ) ! = 0 ) {
row = z - split + 1 ;
match = true ;
} else if ( childQuadrant = = 3 & & ( quad & 4 ) ! = 0 ) {
col = x - split + 1 ;
match = true ;
} else if ( childQuadrant = = 4 & & ( quad & 8 ) ! = 0 ) {
col = x - split + 1 ;
row = z - split + 1 ;
match = true ;
}
if ( match )
return new QuadrantChild ( col , row , spat ) ;
}
}
return null ;
}
/ * *
* Get the interpolated height of the terrain at the specified point .
* @param xz the location to get the height for
* @return Float . NAN if the value does not exist , or the coordinates are outside of the terrain
* /
public float getHeight ( Vector2f xz ) {
// offset
float x = ( float ) ( ( ( xz . x - getWorldTranslation ( ) . x ) / getWorldScale ( ) . x ) + ( float ) ( totalSize - 1 ) / 2f ) ;
float z = ( float ) ( ( ( xz . y - getWorldTranslation ( ) . z ) / getWorldScale ( ) . z ) + ( float ) ( totalSize - 1 ) / 2f ) ;
if ( ! isInside ( ( int ) x , ( int ) z ) )
return Float . NaN ;
float height = getHeight ( ( int ) x , ( int ) z , ( x % 1f ) , ( z % 1f ) ) ;
height * = getWorldScale ( ) . y ;
return height ;
}
/ *
* gets an interpolated value at the specified point
* /
protected float getHeight ( int x , int z , float xm , float zm ) {
QuadrantChild match = findMatchingChild ( x , z ) ;
if ( match ! = null ) {
if ( match . child instanceof TerrainQuad ) {
return ( ( TerrainQuad ) match . child ) . getHeight ( match . col , match . row , xm , zm ) ;
} else if ( match . child instanceof TerrainPatch ) {
return ( ( TerrainPatch ) match . child ) . getHeight ( match . col , match . row , xm , zm ) ;
}
}
return Float . NaN ;
}
public Vector3f getNormal ( Vector2f xz ) {
// offset
float x = ( float ) ( ( ( xz . x - getWorldTranslation ( ) . x ) / getWorldScale ( ) . x ) + ( float ) ( totalSize - 1 ) / 2f ) ;
float z = ( float ) ( ( ( xz . y - getWorldTranslation ( ) . z ) / getWorldScale ( ) . z ) + ( float ) ( totalSize - 1 ) / 2f ) ;
Vector3f normal = getNormal ( x , z , xz ) ;
return normal ;
}
protected Vector3f getNormal ( float x , float z , Vector2f xz ) {
x - = 0 . 5f ;
z - = 0 . 5f ;
float col = FastMath . floor ( x ) ;
float row = FastMath . floor ( z ) ;
boolean onX = false ;
if ( 1 - ( x - col ) - ( z - row ) < 0 ) // what triangle to interpolate on
onX = true ;
// v1--v2 ^
// | / | |
// | / | |
// v3--v4 | Z
// |
// <-------Y
// X
Vector3f n1 = getMeshNormal ( ( int ) FastMath . ceil ( x ) , ( int ) FastMath . ceil ( z ) ) ;
Vector3f n2 = getMeshNormal ( ( int ) FastMath . floor ( x ) , ( int ) FastMath . ceil ( z ) ) ;
Vector3f n3 = getMeshNormal ( ( int ) FastMath . ceil ( x ) , ( int ) FastMath . floor ( z ) ) ;
Vector3f n4 = getMeshNormal ( ( int ) FastMath . floor ( x ) , ( int ) FastMath . floor ( z ) ) ;
return n1 . add ( n2 ) . add ( n3 ) . add ( n4 ) . normalize ( ) ;
}
public void setHeight ( Vector2f xz , float height ) {
List < Vector2f > coord = new ArrayList < Vector2f > ( ) ;
coord . add ( xz ) ;
List < Float > h = new ArrayList < Float > ( ) ;
h . add ( height ) ;
setHeight ( coord , h ) ;
}
public void adjustHeight ( Vector2f xz , float delta ) {
List < Vector2f > coord = new ArrayList < Vector2f > ( ) ;
coord . add ( xz ) ;
List < Float > h = new ArrayList < Float > ( ) ;
h . add ( delta ) ;
adjustHeight ( coord , h ) ;
}
public void setHeight ( List < Vector2f > xz , List < Float > height ) {
setHeight ( xz , height , true ) ;
}
public void adjustHeight ( List < Vector2f > xz , List < Float > height ) {
setHeight ( xz , height , false ) ;
}
protected void setHeight ( List < Vector2f > xz , List < Float > height , boolean overrideHeight ) {
if ( xz . size ( ) ! = height . size ( ) )
throw new IllegalArgumentException ( "Both lists must be the same length!" ) ;
int halfSize = totalSize / 2 ;
List < LocationHeight > locations = new ArrayList < LocationHeight > ( ) ;
// offset
for ( int i = 0 ; i < xz . size ( ) ; i + + ) {
int x = Math . round ( ( xz . get ( i ) . x / getWorldScale ( ) . x ) + halfSize ) ;
int z = Math . round ( ( xz . get ( i ) . y / getWorldScale ( ) . z ) + halfSize ) ;
if ( ! isInside ( x , z ) )
continue ;
locations . add ( new LocationHeight ( x , z , height . get ( i ) ) ) ;
}
setHeight ( locations , overrideHeight ) ; // adjust height of the actual mesh
// signal that the normals need updating
for ( int i = 0 ; i < xz . size ( ) ; i + + )
setNormalRecalcNeeded ( xz . get ( i ) ) ;
}
protected class LocationHeight {
int x ;
int z ;
float h ;
LocationHeight ( ) { }
LocationHeight ( int x , int z , float h ) {
this . x = x ;
this . z = z ;
this . h = h ;
}
}
protected void setHeight ( List < LocationHeight > locations , boolean overrideHeight ) {
if ( children = = null )
return ;
List < LocationHeight > quadLH1 = new ArrayList < LocationHeight > ( ) ;
List < LocationHeight > quadLH2 = new ArrayList < LocationHeight > ( ) ;
List < LocationHeight > quadLH3 = new ArrayList < LocationHeight > ( ) ;
List < LocationHeight > quadLH4 = new ArrayList < LocationHeight > ( ) ;
Spatial quad1 = null ;
Spatial quad2 = null ;
Spatial quad3 = null ;
Spatial quad4 = null ;
// get the child quadrants
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial spat = children . get ( i ) ;
int childQuadrant = 0 ;
if ( spat instanceof TerrainQuad ) {
childQuadrant = ( ( TerrainQuad ) spat ) . getQuadrant ( ) ;
} else if ( spat instanceof TerrainPatch ) {
childQuadrant = ( ( TerrainPatch ) spat ) . getQuadrant ( ) ;
}
if ( childQuadrant = = 1 )
quad1 = spat ;
else if ( childQuadrant = = 2 )
quad2 = spat ;
else if ( childQuadrant = = 3 )
quad3 = spat ;
else if ( childQuadrant = = 4 )
quad4 = spat ;
}
int split = ( size + 1 ) > > 1 ;
// distribute each locationHeight into the quadrant it intersects
for ( LocationHeight lh : locations ) {
int quad = findQuadrant ( lh . x , lh . z ) ;
int col = lh . x ;
int row = lh . z ;
if ( ( quad & 1 ) ! = 0 ) {
quadLH1 . add ( lh ) ;
}
if ( ( quad & 2 ) ! = 0 ) {
row = lh . z - split + 1 ;
quadLH2 . add ( new LocationHeight ( lh . x , row , lh . h ) ) ;
}
if ( ( quad & 4 ) ! = 0 ) {
col = lh . x - split + 1 ;
quadLH3 . add ( new LocationHeight ( col , lh . z , lh . h ) ) ;
}
if ( ( quad & 8 ) ! = 0 ) {
col = lh . x - split + 1 ;
row = lh . z - split + 1 ;
quadLH4 . add ( new LocationHeight ( col , row , lh . h ) ) ;
}
}
// send the locations to the children
if ( ! quadLH1 . isEmpty ( ) ) {
if ( quad1 instanceof TerrainQuad )
( ( TerrainQuad ) quad1 ) . setHeight ( quadLH1 , overrideHeight ) ;
else if ( quad1 instanceof TerrainPatch )
( ( TerrainPatch ) quad1 ) . setHeight ( quadLH1 , overrideHeight ) ;
}
if ( ! quadLH2 . isEmpty ( ) ) {
if ( quad2 instanceof TerrainQuad )
( ( TerrainQuad ) quad2 ) . setHeight ( quadLH2 , overrideHeight ) ;
else if ( quad2 instanceof TerrainPatch )
( ( TerrainPatch ) quad2 ) . setHeight ( quadLH2 , overrideHeight ) ;
}
if ( ! quadLH3 . isEmpty ( ) ) {
if ( quad3 instanceof TerrainQuad )
( ( TerrainQuad ) quad3 ) . setHeight ( quadLH3 , overrideHeight ) ;
else if ( quad3 instanceof TerrainPatch )
( ( TerrainPatch ) quad3 ) . setHeight ( quadLH3 , overrideHeight ) ;
}
if ( ! quadLH4 . isEmpty ( ) ) {
if ( quad4 instanceof TerrainQuad )
( ( TerrainQuad ) quad4 ) . setHeight ( quadLH4 , overrideHeight ) ;
else if ( quad4 instanceof TerrainPatch )
( ( TerrainPatch ) quad4 ) . setHeight ( quadLH4 , overrideHeight ) ;
}
}
protected boolean isPointOnTerrain ( int x , int z ) {
return ( x > = 0 & & x < = totalSize & & z > = 0 & & z < = totalSize ) ;
}
public int getTerrainSize ( ) {
return totalSize ;
}
// a position can be in multiple quadrants, so use a bit anded value.
private int findQuadrant ( int x , int y ) {
int split = ( size + 1 ) > > 1 ;
int quads = 0 ;
if ( x < split & & y < split )
quads | = 1 ;
if ( x < split & & y > = split - 1 )
quads | = 2 ;
if ( x > = split - 1 & & y < split )
quads | = 4 ;
if ( x > = split - 1 & & y > = split - 1 )
quads | = 8 ;
return quads ;
}
/ * *
* lock or unlock the meshes of this terrain .
* Locked meshes are uneditable but have better performance .
* @param locked or unlocked
* /
public void setLocked ( boolean locked ) {
for ( int i = 0 ; i < this . getQuantity ( ) ; i + + ) {
if ( this . getChild ( i ) instanceof TerrainQuad ) {
( ( TerrainQuad ) getChild ( i ) ) . setLocked ( locked ) ;
} else if ( this . getChild ( i ) instanceof TerrainPatch ) {
if ( locked )
( ( TerrainPatch ) getChild ( i ) ) . lockMesh ( ) ;
else
( ( TerrainPatch ) getChild ( i ) ) . unlockMesh ( ) ;
}
}
}
public int getQuadrant ( ) {
return quadrant ;
}
public void setQuadrant ( short quadrant ) {
this . quadrant = quadrant ;
}
protected TerrainPatch getPatch ( int quad ) {
if ( children ! = null )
for ( int x = children . size ( ) ; - - x > = 0 ; ) {
Spatial child = children . get ( x ) ;
if ( child instanceof TerrainPatch ) {
TerrainPatch tb = ( TerrainPatch ) child ;
if ( tb . getQuadrant ( ) = = quad )
return tb ;
}
}
return null ;
}
protected TerrainQuad getQuad ( int quad ) {
if ( quad = = 0 )
return this ;
if ( children ! = null )
for ( int x = children . size ( ) ; - - x > = 0 ; ) {
Spatial child = children . get ( x ) ;
if ( child instanceof TerrainQuad ) {
TerrainQuad tq = ( TerrainQuad ) child ;
if ( tq . getQuadrant ( ) = = quad )
return tq ;
}
}
return null ;
}
protected TerrainPatch findRightPatch ( TerrainPatch tp ) {
if ( tp . getQuadrant ( ) = = 1 )
return getPatch ( 3 ) ;
else if ( tp . getQuadrant ( ) = = 2 )
return getPatch ( 4 ) ;
else if ( tp . getQuadrant ( ) = = 3 ) {
// find the patch to the right and ask it for child 1.
TerrainQuad quad = findRightQuad ( ) ;
if ( quad ! = null )
return quad . getPatch ( 1 ) ;
} else if ( tp . getQuadrant ( ) = = 4 ) {
// find the patch to the right and ask it for child 2.
TerrainQuad quad = findRightQuad ( ) ;
if ( quad ! = null )
return quad . getPatch ( 2 ) ;
}
return null ;
}
protected TerrainPatch findDownPatch ( TerrainPatch tp ) {
if ( tp . getQuadrant ( ) = = 1 )
return getPatch ( 2 ) ;
else if ( tp . getQuadrant ( ) = = 3 )
return getPatch ( 4 ) ;
else if ( tp . getQuadrant ( ) = = 2 ) {
// find the patch below and ask it for child 1.
TerrainQuad quad = findDownQuad ( ) ;
if ( quad ! = null )
return quad . getPatch ( 1 ) ;
} else if ( tp . getQuadrant ( ) = = 4 ) {
TerrainQuad quad = findDownQuad ( ) ;
if ( quad ! = null )
return quad . getPatch ( 3 ) ;
}
return null ;
}
protected TerrainPatch findTopPatch ( TerrainPatch tp ) {
if ( tp . getQuadrant ( ) = = 2 )
return getPatch ( 1 ) ;
else if ( tp . getQuadrant ( ) = = 4 )
return getPatch ( 3 ) ;
else if ( tp . getQuadrant ( ) = = 1 ) {
// find the patch above and ask it for child 2.
TerrainQuad quad = findTopQuad ( ) ;
if ( quad ! = null )
return quad . getPatch ( 2 ) ;
} else if ( tp . getQuadrant ( ) = = 3 ) {
TerrainQuad quad = findTopQuad ( ) ;
if ( quad ! = null )
return quad . getPatch ( 4 ) ;
}
return null ;
}
protected TerrainPatch findLeftPatch ( TerrainPatch tp ) {
if ( tp . getQuadrant ( ) = = 3 )
return getPatch ( 1 ) ;
else if ( tp . getQuadrant ( ) = = 4 )
return getPatch ( 2 ) ;
else if ( tp . getQuadrant ( ) = = 1 ) {
// find the patch above and ask it for child 3.
TerrainQuad quad = findLeftQuad ( ) ;
if ( quad ! = null )
return quad . getPatch ( 3 ) ;
} else if ( tp . getQuadrant ( ) = = 2 ) {
TerrainQuad quad = findLeftQuad ( ) ;
if ( quad ! = null )
return quad . getPatch ( 4 ) ;
}
return null ;
}
protected TerrainQuad findRightQuad ( ) {
boolean useFinder = false ;
if ( getParent ( ) = = null | | ! ( getParent ( ) instanceof TerrainQuad ) ) {
if ( neighbourFinder = = null )
return null ;
else
useFinder = true ;
}
TerrainQuad pQuad = null ;
if ( ! useFinder )
pQuad = ( TerrainQuad ) getParent ( ) ;
if ( quadrant = = 1 )
return pQuad . getQuad ( 3 ) ;
else if ( quadrant = = 2 )
return pQuad . getQuad ( 4 ) ;
else if ( quadrant = = 3 ) {
TerrainQuad quad = pQuad . findRightQuad ( ) ;
if ( quad ! = null )
return quad . getQuad ( 1 ) ;
} else if ( quadrant = = 4 ) {
TerrainQuad quad = pQuad . findRightQuad ( ) ;
if ( quad ! = null )
return quad . getQuad ( 2 ) ;
} else if ( quadrant = = 0 ) {
// at the top quad
if ( useFinder ) {
TerrainQuad quad = neighbourFinder . getRightQuad ( this ) ;
return quad ;
}
}
return null ;
}
protected TerrainQuad findDownQuad ( ) {
boolean useFinder = false ;
if ( getParent ( ) = = null | | ! ( getParent ( ) instanceof TerrainQuad ) ) {
if ( neighbourFinder = = null )
return null ;
else
useFinder = true ;
}
TerrainQuad pQuad = null ;
if ( ! useFinder )
pQuad = ( TerrainQuad ) getParent ( ) ;
if ( quadrant = = 1 )
return pQuad . getQuad ( 2 ) ;
else if ( quadrant = = 3 )
return pQuad . getQuad ( 4 ) ;
else if ( quadrant = = 2 ) {
TerrainQuad quad = pQuad . findDownQuad ( ) ;
if ( quad ! = null )
return quad . getQuad ( 1 ) ;
} else if ( quadrant = = 4 ) {
TerrainQuad quad = pQuad . findDownQuad ( ) ;
if ( quad ! = null )
return quad . getQuad ( 3 ) ;
} else if ( quadrant = = 0 ) {
// at the top quad
if ( useFinder ) {
TerrainQuad quad = neighbourFinder . getDownQuad ( this ) ;
return quad ;
}
}
return null ;
}
protected TerrainQuad findTopQuad ( ) {
boolean useFinder = false ;
if ( getParent ( ) = = null | | ! ( getParent ( ) instanceof TerrainQuad ) ) {
if ( neighbourFinder = = null )
return null ;
else
useFinder = true ;
}
TerrainQuad pQuad = null ;
if ( ! useFinder )
pQuad = ( TerrainQuad ) getParent ( ) ;
if ( quadrant = = 2 )
return pQuad . getQuad ( 1 ) ;
else if ( quadrant = = 4 )
return pQuad . getQuad ( 3 ) ;
else if ( quadrant = = 1 ) {
TerrainQuad quad = pQuad . findTopQuad ( ) ;
if ( quad ! = null )
return quad . getQuad ( 2 ) ;
} else if ( quadrant = = 3 ) {
TerrainQuad quad = pQuad . findTopQuad ( ) ;
if ( quad ! = null )
return quad . getQuad ( 4 ) ;
} else if ( quadrant = = 0 ) {
// at the top quad
if ( useFinder ) {
TerrainQuad quad = neighbourFinder . getTopQuad ( this ) ;
return quad ;
}
}
return null ;
}
protected TerrainQuad findLeftQuad ( ) {
boolean useFinder = false ;
if ( getParent ( ) = = null | | ! ( getParent ( ) instanceof TerrainQuad ) ) {
if ( neighbourFinder = = null )
return null ;
else
useFinder = true ;
}
TerrainQuad pQuad = null ;
if ( ! useFinder )
pQuad = ( TerrainQuad ) getParent ( ) ;
if ( quadrant = = 3 )
return pQuad . getQuad ( 1 ) ;
else if ( quadrant = = 4 )
return pQuad . getQuad ( 2 ) ;
else if ( quadrant = = 1 ) {
TerrainQuad quad = pQuad . findLeftQuad ( ) ;
if ( quad ! = null )
return quad . getQuad ( 3 ) ;
} else if ( quadrant = = 2 ) {
TerrainQuad quad = pQuad . findLeftQuad ( ) ;
if ( quad ! = null )
return quad . getQuad ( 4 ) ;
} else if ( quadrant = = 0 ) {
// at the top quad
if ( useFinder ) {
TerrainQuad quad = neighbourFinder . getLeftQuad ( this ) ;
return quad ;
}
}
return null ;
}
/ * *
* Find what terrain patches need normal recalculations and update
* their normals ;
* /
protected void fixNormals ( BoundingBox affectedArea ) {
if ( children = = null )
return ;
// go through the children and see if they collide with the affectedAreaBBox
// if they do, then update their normals
for ( int x = children . size ( ) ; - - x > = 0 ; ) {
Spatial child = children . get ( x ) ;
if ( child instanceof TerrainQuad ) {
if ( affectedArea ! = null & & affectedArea . intersects ( ( ( TerrainQuad ) child ) . getWorldBound ( ) ) )
( ( TerrainQuad ) child ) . fixNormals ( affectedArea ) ;
} else if ( child instanceof TerrainPatch ) {
if ( affectedArea ! = null & & affectedArea . intersects ( ( ( TerrainPatch ) child ) . getWorldBound ( ) ) )
( ( TerrainPatch ) child ) . updateNormals ( ) ; // recalculate the patch's normals
}
}
}
/ * *
* fix the normals on the edge of the terrain patches .
* /
protected void fixNormalEdges ( BoundingBox affectedArea ) {
if ( children = = null )
return ;
for ( int x = children . size ( ) ; - - x > = 0 ; ) {
Spatial child = children . get ( x ) ;
if ( child instanceof TerrainQuad ) {
if ( affectedArea ! = null & & affectedArea . intersects ( ( ( TerrainQuad ) child ) . getWorldBound ( ) ) )
( ( TerrainQuad ) child ) . fixNormalEdges ( affectedArea ) ;
} else if ( child instanceof TerrainPatch ) {
if ( affectedArea ! = null & & ! affectedArea . intersects ( ( ( TerrainPatch ) child ) . getWorldBound ( ) ) ) // if doesn't intersect, continue
continue ;
TerrainPatch tp = ( TerrainPatch ) child ;
TerrainPatch right = findRightPatch ( tp ) ;
TerrainPatch bottom = findDownPatch ( tp ) ;
TerrainPatch top = findTopPatch ( tp ) ;
TerrainPatch left = findLeftPatch ( tp ) ;
TerrainPatch topLeft = null ;
if ( top ! = null )
topLeft = findLeftPatch ( top ) ;
TerrainPatch bottomRight = null ;
if ( right ! = null )
bottomRight = findDownPatch ( right ) ;
TerrainPatch topRight = null ;
if ( top ! = null )
topRight = findRightPatch ( top ) ;
TerrainPatch bottomLeft = null ;
if ( left ! = null )
bottomLeft = findDownPatch ( left ) ;
tp . fixNormalEdges ( right , bottom , top , left , bottomRight , bottomLeft , topRight , topLeft ) ;
}
} // for each child
}
@Override
public int collideWith ( Collidable other , CollisionResults results ) {
int total = 0 ;
if ( other instanceof Ray )
return collideWithRay ( ( Ray ) other , results ) ;
// if it didn't collide with this bbox, return
if ( other instanceof BoundingVolume )
if ( ! this . getWorldBound ( ) . intersects ( ( BoundingVolume ) other ) )
return total ;
for ( Spatial child : children ) {
total + = child . collideWith ( other , results ) ;
}
return total ;
}
/ * *
* Gather the terrain patches that intersect the given ray ( toTest ) .
* This only tests the bounding boxes
* @param toTest
* @param results
* /
public void findPick ( Ray toTest , List < TerrainPickData > results ) {
if ( getWorldBound ( ) ! = null ) {
if ( getWorldBound ( ) . intersects ( toTest ) ) {
// further checking needed.
for ( int i = 0 ; i < getQuantity ( ) ; i + + ) {
if ( children . get ( i ) instanceof TerrainPatch ) {
TerrainPatch tp = ( TerrainPatch ) children . get ( i ) ;
tp . ensurePositiveVolumeBBox ( ) ;
if ( tp . getWorldBound ( ) . intersects ( toTest ) ) {
CollisionResults cr = new CollisionResults ( ) ;
toTest . collideWith ( tp . getWorldBound ( ) , cr ) ;
if ( cr ! = null & & cr . getClosestCollision ( ) ! = null ) {
cr . getClosestCollision ( ) . getDistance ( ) ;
results . add ( new TerrainPickData ( tp , cr . getClosestCollision ( ) ) ) ;
}
}
}
else if ( children . get ( i ) instanceof TerrainQuad ) {
( ( TerrainQuad ) children . get ( i ) ) . findPick ( toTest , results ) ;
}
}
}
}
}
/ * *
* Retrieve all Terrain Patches from all children and store them
* in the ' holder ' list
* @param holder must not be null , will be populated when returns
* /
public void getAllTerrainPatches ( List < TerrainPatch > holder ) {
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial child = children . get ( i ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . getAllTerrainPatches ( holder ) ;
} else if ( child instanceof TerrainPatch ) {
holder . add ( ( TerrainPatch ) child ) ;
}
}
}
}
public void getAllTerrainPatchesWithTranslation ( Map < TerrainPatch , Vector3f > holder , Vector3f translation ) {
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial child = children . get ( i ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . getAllTerrainPatchesWithTranslation ( holder , translation . clone ( ) . add ( child . getLocalTranslation ( ) ) ) ;
} else if ( child instanceof TerrainPatch ) {
//if (holder.size() < 4)
holder . put ( ( TerrainPatch ) child , translation . clone ( ) . add ( child . getLocalTranslation ( ) ) ) ;
}
}
}
}
@Override
public void read ( JmeImporter e ) throws IOException {
super . read ( e ) ;
InputCapsule c = e . getCapsule ( this ) ;
size = c . readInt ( "size" , 0 ) ;
stepScale = ( Vector3f ) c . readSavable ( "stepScale" , null ) ;
offset = ( Vector2f ) c . readSavable ( "offset" , new Vector2f ( 0 , 0 ) ) ;
offsetAmount = c . readFloat ( "offsetAmount" , 0 ) ;
quadrant = c . readInt ( "quadrant" , 0 ) ;
totalSize = c . readInt ( "totalSize" , 0 ) ;
//lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator());
//lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null);
if ( ! ( getParent ( ) instanceof TerrainQuad ) ) {
BoundingBox all = new BoundingBox ( getWorldTranslation ( ) , totalSize , totalSize , totalSize ) ;
affectedAreaBBox = all ;
updateNormals ( ) ;
}
}
@Override
public void write ( JmeExporter e ) throws IOException {
super . write ( e ) ;
OutputCapsule c = e . getCapsule ( this ) ;
c . write ( size , "size" , 0 ) ;
c . write ( totalSize , "totalSize" , 0 ) ;
c . write ( stepScale , "stepScale" , null ) ;
c . write ( offset , "offset" , new Vector2f ( 0 , 0 ) ) ;
c . write ( offsetAmount , "offsetAmount" , 0 ) ;
c . write ( quadrant , "quadrant" , 0 ) ;
//c.write(lodCalculatorFactory, "lodCalculatorFactory", null);
//c.write(lodCalculator, "lodCalculator", null);
}
@Override
public TerrainQuad clone ( ) {
return this . clone ( true ) ;
}
@Override
public TerrainQuad clone ( boolean cloneMaterials ) {
TerrainQuad quadClone = ( TerrainQuad ) super . clone ( cloneMaterials ) ;
quadClone . name = name . toString ( ) ;
quadClone . size = size ;
quadClone . totalSize = totalSize ;
if ( stepScale ! = null ) {
quadClone . stepScale = stepScale . clone ( ) ;
}
if ( offset ! = null ) {
quadClone . offset = offset . clone ( ) ;
}
quadClone . offsetAmount = offsetAmount ;
quadClone . quadrant = quadrant ;
//quadClone.lodCalculatorFactory = lodCalculatorFactory.clone();
//quadClone.lodCalculator = lodCalculator.clone();
TerrainLodControl lodControlCloned = this . getControl ( TerrainLodControl . class ) ;
TerrainLodControl lodControl = quadClone . getControl ( TerrainLodControl . class ) ;
if ( lodControlCloned ! = null & & ! ( getParent ( ) instanceof TerrainQuad ) ) {
//lodControlCloned.setLodCalculator(lodControl.getLodCalculator().clone());
}
NormalRecalcControl normalControl = getControl ( NormalRecalcControl . class ) ;
if ( normalControl ! = null )
normalControl . setTerrain ( this ) ;
return quadClone ;
}
/ * *
* Called internally by com . jme3 . util . clone . Cloner . Do not call directly .
* /
@Override
public void cloneFields ( Cloner cloner , Object original ) {
super . cloneFields ( cloner , original ) ;
this . stepScale = cloner . clone ( stepScale ) ;
this . offset = cloner . clone ( offset ) ;
// This was not cloned before... I think that's a mistake.
this . affectedAreaBBox = cloner . clone ( affectedAreaBBox ) ;
// picker is not cloneable and not cloned. This also seems like
// a mistake if you ever load the same terrain twice.
// this.picker = cloner.clone(picker);
// neighbourFinder is also not cloned. Maybe that's ok.
}
@Override
protected void setParent ( Node parent ) {
super . setParent ( parent ) ;
if ( parent = = null ) {
// if the terrain is being detached
clearCaches ( ) ;
}
}
/ * *
* Removes any cached references this terrain is holding , in particular
* the TerrainPatch ' s neighbour references .
* This is called automatically when the root terrainQuad is detached from
* its parent or if setParent ( null ) is called .
* /
public void clearCaches ( ) {
if ( children ! = null ) {
for ( int i = children . size ( ) ; - - i > = 0 ; ) {
Spatial child = children . get ( i ) ;
if ( child instanceof TerrainQuad ) {
( ( TerrainQuad ) child ) . clearCaches ( ) ;
} else if ( child instanceof TerrainPatch ) {
( ( TerrainPatch ) child ) . clearCaches ( ) ;
}
}
}
}
public int getMaxLod ( ) {
if ( maxLod < 0 )
maxLod = Math . max ( 1 , ( int ) ( FastMath . log ( size - 1 ) / FastMath . log ( 2 ) ) - 1 ) ; // -1 forces our minimum of 4 triangles wide
return maxLod ;
}
public int getPatchSize ( ) {
return patchSize ;
}
public int getTotalSize ( ) {
return totalSize ;
}
public float [ ] getHeightMap ( ) {
float [ ] hm = null ;
int length = ( ( size - 1 ) / 2 ) + 1 ;
int area = size * size ;
hm = new float [ area ] ;
if ( getChildren ( ) ! = null & & ! getChildren ( ) . isEmpty ( ) ) {
float [ ] ul = null , ur = null , bl = null , br = null ;
// get the child heightmaps
if ( getChild ( 0 ) instanceof TerrainPatch ) {
for ( Spatial s : getChildren ( ) ) {
if ( ( ( TerrainPatch ) s ) . getQuadrant ( ) = = 1 )
ul = ( ( TerrainPatch ) s ) . getHeightMap ( ) ;
else if ( ( ( TerrainPatch ) s ) . getQuadrant ( ) = = 2 )
bl = ( ( TerrainPatch ) s ) . getHeightMap ( ) ;
else if ( ( ( TerrainPatch ) s ) . getQuadrant ( ) = = 3 )
ur = ( ( TerrainPatch ) s ) . getHeightMap ( ) ;
else if ( ( ( TerrainPatch ) s ) . getQuadrant ( ) = = 4 )
br = ( ( TerrainPatch ) s ) . getHeightMap ( ) ;
}
}
else {
ul = getQuad ( 1 ) . getHeightMap ( ) ;
bl = getQuad ( 2 ) . getHeightMap ( ) ;
ur = getQuad ( 3 ) . getHeightMap ( ) ;
br = getQuad ( 4 ) . getHeightMap ( ) ;
}
// combine them into a single heightmap
// first upper blocks
for ( int y = 0 ; y < length ; y + + ) { // rows
for ( int x1 = 0 ; x1 < length ; x1 + + ) {
int row = y * size ;
hm [ row + x1 ] = ul [ y * length + x1 ] ;
}
for ( int x2 = 1 ; x2 < length ; x2 + + ) {
int row = y * size + length ;
hm [ row + x2 - 1 ] = ur [ y * length + x2 ] ;
}
}
// second lower blocks
int rowOffset = size * length ;
for ( int y = 1 ; y < length ; y + + ) { // rows
for ( int x1 = 0 ; x1 < length ; x1 + + ) {
int row = ( y - 1 ) * size ;
hm [ rowOffset + row + x1 ] = bl [ y * length + x1 ] ;
}
for ( int x2 = 1 ; x2 < length ; x2 + + ) {
int row = ( y - 1 ) * size + length ;
hm [ rowOffset + row + x2 - 1 ] = br [ y * length + x2 ] ;
}
}
}
return hm ;
}
}