@ -50,6 +50,7 @@ import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight ;
import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil ;
import com.jme3.util.BufferUtils ;
import com.jme3.util.clone.Cloner ;
import java.io.IOException ;
import java.nio.Buffer ;
import java.nio.FloatBuffer ;
@ -65,18 +66,18 @@ import java.util.List;
* That uses a geo - mipmapping algorithm to change the index buffer of the mesh .
* The mesh is a triangle strip . In wireframe mode you might notice some strange lines , these are degenerate
* triangles generated by the geoMipMap algorithm and can be ignored . The video card removes them at almost no cost .
*
*
* Each patch needs to know its neighbour ' s LOD so it can seam its edges with them , in case the neighbour has a different
* LOD . If this doesn ' t happen , you will see gaps .
*
*
* The LOD value is most detailed at zero . It gets less detailed the higher the LOD value until you reach maxLod , which
* is a mathematical limit on the number of times the ' size ' of the patch can be divided by two . However there is a - 1 to that
* for now until I add in a custom index buffer calculation for that max level , the current algorithm does not go that far .
*
* You can supply a LodThresholdCalculator for use in determining when the LOD should change . It ' s API will no doubt change
* in the near future . Right now it defaults to just changing LOD every two patch sizes . So if a patch has a size of 65 ,
*
* You can supply a LodThresholdCalculator for use in determining when the LOD should change . It ' s API will no doubt change
* in the near future . Right now it defaults to just changing LOD every two patch sizes . So if a patch has a size of 65 ,
* then the LOD changes every 130 units away .
*
*
* @author Brent Owens
* /
public class TerrainPatch extends Geometry {
@ -118,7 +119,7 @@ public class TerrainPatch extends Geometry {
super ( "TerrainPatch" ) ;
setBatchHint ( BatchHint . Never ) ;
}
public TerrainPatch ( String name ) {
super ( name ) ;
setBatchHint ( BatchHint . Never ) ;
@ -221,7 +222,7 @@ public class TerrainPatch extends Geometry {
public FloatBuffer getHeightmap ( ) {
return BufferUtils . createFloatBuffer ( geomap . getHeightArray ( ) ) ;
}
public float [ ] getHeightMap ( ) {
return geomap . getHeightArray ( ) ;
}
@ -256,7 +257,7 @@ public class TerrainPatch extends Geometry {
idxB = geomap . writeIndexArrayLodVariable ( pow , ( int ) Math . pow ( 2 , utp . getRightLod ( ) ) , ( int ) Math . pow ( 2 , utp . getTopLod ( ) ) , ( int ) Math . pow ( 2 , utp . getLeftLod ( ) ) , ( int ) Math . pow ( 2 , utp . getBottomLod ( ) ) , totalSize ) ;
else
idxB = geomap . writeIndexArrayLodDiff ( pow , right , top , left , bottom , totalSize ) ;
Buffer b ;
if ( idxB . getBuffer ( ) instanceof IntBuffer )
b = ( IntBuffer ) idxB . getBuffer ( ) ;
@ -277,14 +278,14 @@ public class TerrainPatch extends Geometry {
return store . set ( getMesh ( ) . getFloatBuffer ( Type . TexCoord ) . get ( idx * 2 ) ,
getMesh ( ) . getFloatBuffer ( Type . TexCoord ) . get ( idx * 2 + 1 ) ) ;
}
public float getHeightmapHeight ( float x , float z ) {
if ( x < 0 | | z < 0 | | x > = size | | z > = size )
return 0 ;
int idx = ( int ) ( z * size + x ) ;
return getMesh ( ) . getFloatBuffer ( Type . Position ) . get ( idx * 3 + 1 ) ; // 3 floats per entry (x,y,z), the +1 is to get the Y
}
/ * *
* Get the triangle of this geometry at the specified local coordinate .
* @param x local to the terrain patch
@ -306,7 +307,7 @@ public class TerrainPatch extends Geometry {
}
protected void setHeight ( List < LocationHeight > locationHeights , boolean overrideHeight ) {
for ( LocationHeight lh : locationHeights ) {
if ( lh . x < 0 | | lh . z < 0 | | lh . x > = size | | lh . z > = size )
continue ;
@ -317,7 +318,7 @@ public class TerrainPatch extends Geometry {
float h = getMesh ( ) . getFloatBuffer ( Type . Position ) . get ( idx * 3 + 1 ) ;
geomap . getHeightArray ( ) [ idx ] = h + lh . h ;
}
}
FloatBuffer newVertexBuffer = geomap . writeVertexArray ( null , stepScale , false ) ;
@ -351,7 +352,7 @@ public class TerrainPatch extends Geometry {
TB . setUpdateNeeded ( ) ;
BB . setUpdateNeeded ( ) ;
}
/ * *
* Matches the normals along the edge of the patch with the neighbours .
* Computes the normals for the right , bottom , left , and top edges of the
@ -364,7 +365,7 @@ public class TerrainPatch extends Geometry {
* * - - - x - - - *
* |
* *
* It works across the right side of the patch , from the top down to
* It works across the right side of the patch , from the top down to
* the bottom . Then it works on the bottom side of the patch , from the
* left to the right .
* /
@ -388,9 +389,9 @@ public class TerrainPatch extends Geometry {
Vector3f binormal = new Vector3f ( ) ;
Vector3f normal = new Vector3f ( ) ;
int s = this . getSize ( ) - 1 ;
if ( right ! = null ) { // right side, works its way down
for ( int i = 0 ; i < s + 1 ; i + + ) {
rootPoint . set ( 0 , this . getHeightmapHeight ( s , i ) , 0 ) ;
@ -399,26 +400,26 @@ public class TerrainPatch extends Geometry {
if ( i = = 0 ) { // top point
bottomPoint . set ( 0 , this . getHeightmapHeight ( s , i + 1 ) , 1 ) ;
if ( top = = null ) {
averageNormalsTangents ( null , rootPoint , leftPoint , bottomPoint , rightPoint , normal , tangent , binormal ) ;
setInBuffer ( this . getMesh ( ) , s , normal , tangent , binormal ) ;
setInBuffer ( right . getMesh ( ) , 0 , normal , tangent , binormal ) ;
} else {
topPoint . set ( 0 , top . getHeightmapHeight ( s , s - 1 ) , - 1 ) ;
averageNormalsTangents ( topPoint , rootPoint , leftPoint , bottomPoint , rightPoint , normal , tangent , binormal ) ;
setInBuffer ( this . getMesh ( ) , s , normal , tangent , binormal ) ;
setInBuffer ( right . getMesh ( ) , 0 , normal , tangent , binormal ) ;
setInBuffer ( top . getMesh ( ) , ( s + 1 ) * ( s + 1 ) - 1 , normal , tangent , binormal ) ;
if ( topRight ! = null ) {
// setInBuffer(topRight.getMesh(), (s+1)*s, normal, tangent, binormal);
}
}
} else if ( i = = s ) { // bottom point
topPoint . set ( 0 , this . getHeightmapHeight ( s , s - 1 ) , - 1 ) ;
if ( bottom = = null ) {
averageNormalsTangents ( topPoint , rootPoint , leftPoint , null , rightPoint , normal , tangent , binormal ) ;
setInBuffer ( this . getMesh ( ) , ( s + 1 ) * ( s + 1 ) - 1 , normal , tangent , binormal ) ;
@ -429,7 +430,7 @@ public class TerrainPatch extends Geometry {
setInBuffer ( this . getMesh ( ) , ( s + 1 ) * ( s + 1 ) - 1 , normal , tangent , binormal ) ;
setInBuffer ( right . getMesh ( ) , ( s + 1 ) * s , normal , tangent , binormal ) ;
setInBuffer ( bottom . getMesh ( ) , s , normal , tangent , binormal ) ;
if ( bottomRight ! = null ) {
// setInBuffer(bottomRight.getMesh(), 0, normal, tangent, binormal);
}
@ -449,41 +450,41 @@ public class TerrainPatch extends Geometry {
rootPoint . set ( 0 , this . getHeightmapHeight ( 0 , i ) , 0 ) ;
leftPoint . set ( - 1 , left . getHeightmapHeight ( s - 1 , i ) , 0 ) ;
rightPoint . set ( 1 , this . getHeightmapHeight ( 1 , i ) , 0 ) ;
if ( i = = 0 ) { // top point
bottomPoint . set ( 0 , this . getHeightmapHeight ( 0 , i + 1 ) , 1 ) ;
if ( top = = null ) {
averageNormalsTangents ( null , rootPoint , leftPoint , bottomPoint , rightPoint , normal , tangent , binormal ) ;
setInBuffer ( this . getMesh ( ) , 0 , normal , tangent , binormal ) ;
setInBuffer ( left . getMesh ( ) , s , normal , tangent , binormal ) ;
} else {
topPoint . set ( 0 , top . getHeightmapHeight ( 0 , s - 1 ) , - 1 ) ;
averageNormalsTangents ( topPoint , rootPoint , leftPoint , bottomPoint , rightPoint , normal , tangent , binormal ) ;
setInBuffer ( this . getMesh ( ) , 0 , normal , tangent , binormal ) ;
setInBuffer ( left . getMesh ( ) , s , normal , tangent , binormal ) ;
setInBuffer ( top . getMesh ( ) , ( s + 1 ) * s , normal , tangent , binormal ) ;
if ( topLeft ! = null ) {
// setInBuffer(topLeft.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);
}
}
} else if ( i = = s ) { // bottom point
topPoint . set ( 0 , this . getHeightmapHeight ( 0 , i - 1 ) , - 1 ) ;
if ( bottom = = null ) {
averageNormalsTangents ( topPoint , rootPoint , leftPoint , null , rightPoint , normal , tangent , binormal ) ;
setInBuffer ( this . getMesh ( ) , ( s + 1 ) * ( s ) , normal , tangent , binormal ) ;
setInBuffer ( left . getMesh ( ) , ( s + 1 ) * ( s + 1 ) - 1 , normal , tangent , binormal ) ;
} else {
bottomPoint . set ( 0 , bottom . getHeightmapHeight ( 0 , 1 ) , 1 ) ;
averageNormalsTangents ( topPoint , rootPoint , leftPoint , bottomPoint , rightPoint , normal , tangent , binormal ) ;
setInBuffer ( this . getMesh ( ) , ( s + 1 ) * ( s ) , normal , tangent , binormal ) ;
setInBuffer ( left . getMesh ( ) , ( s + 1 ) * ( s + 1 ) - 1 , normal , tangent , binormal ) ;
setInBuffer ( bottom . getMesh ( ) , 0 , normal , tangent , binormal ) ;
if ( bottomLeft ! = null ) {
// setInBuffer(bottomLeft.getMesh(), s, normal, tangent, binormal);
}
@ -491,7 +492,7 @@ public class TerrainPatch extends Geometry {
} else { // all in the middle
topPoint . set ( 0 , this . getHeightmapHeight ( 0 , i - 1 ) , - 1 ) ;
bottomPoint . set ( 0 , this . getHeightmapHeight ( 0 , i + 1 ) , 1 ) ;
averageNormalsTangents ( topPoint , rootPoint , leftPoint , bottomPoint , rightPoint , normal , tangent , binormal ) ;
setInBuffer ( this . getMesh ( ) , ( s + 1 ) * ( i ) , normal , tangent , binormal ) ;
setInBuffer ( left . getMesh ( ) , ( s + 1 ) * ( i + 1 ) - 1 , normal , tangent , binormal ) ;
@ -504,14 +505,14 @@ public class TerrainPatch extends Geometry {
rootPoint . set ( 0 , this . getHeightmapHeight ( i , 0 ) , 0 ) ;
topPoint . set ( 0 , top . getHeightmapHeight ( i , s - 1 ) , - 1 ) ;
bottomPoint . set ( 0 , this . getHeightmapHeight ( i , 1 ) , 1 ) ;
if ( i = = 0 ) { // left corner
// handled by left side pass
} else if ( i = = s ) { // right corner
// handled by this patch when it does its right side
} else { // all in the middle
leftPoint . set ( - 1 , this . getHeightmapHeight ( i - 1 , 0 ) , 0 ) ;
rightPoint . set ( 1 , this . getHeightmapHeight ( i + 1 , 0 ) , 0 ) ;
@ -520,9 +521,9 @@ public class TerrainPatch extends Geometry {
setInBuffer ( top . getMesh ( ) , ( s + 1 ) * ( s ) + i , normal , tangent , binormal ) ;
}
}
}
if ( bottom ! = null ) { // bottom side, works its way right
for ( int i = 0 ; i < s + 1 ; i + + ) {
rootPoint . set ( 0 , this . getHeightmapHeight ( i , s ) , 0 ) ;
@ -531,11 +532,11 @@ public class TerrainPatch extends Geometry {
if ( i = = 0 ) { // left
// handled by the left side pass
} else if ( i = = s ) { // right
// handled by the right side pass
} else { // all in the middle
leftPoint . set ( - 1 , this . getHeightmapHeight ( i - 1 , s ) , 0 ) ;
rightPoint . set ( 1 , this . getHeightmapHeight ( i + 1 , s ) , 0 ) ;
@ -544,22 +545,22 @@ public class TerrainPatch extends Geometry {
setInBuffer ( bottom . getMesh ( ) , i , normal , tangent , binormal ) ;
}
}
}
}
protected void averageNormalsTangents (
Vector3f topPoint ,
Vector3f rootPoint ,
Vector3f leftPoint ,
Vector3f bottomPoint ,
Vector3f leftPoint ,
Vector3f bottomPoint ,
Vector3f rightPoint ,
Vector3f normal ,
Vector3f tangent ,
Vector3f binormal )
{
Vector3f scale = getWorldScale ( ) ;
Vector3f n1 = new Vector3f ( 0 , 0 , 0 ) ;
if ( topPoint ! = null & & leftPoint ! = null ) {
n1 . set ( calculateNormal ( topPoint . mult ( scale ) , rootPoint . mult ( scale ) , leftPoint . mult ( scale ) ) ) ;
@ -576,12 +577,12 @@ public class TerrainPatch extends Geometry {
if ( rightPoint ! = null & & topPoint ! = null ) {
n4 . set ( calculateNormal ( rightPoint . mult ( scale ) , rootPoint . mult ( scale ) , topPoint . mult ( scale ) ) ) ;
}
//if (bottomPoint != null && rightPoint != null && rootTex != null && rightTex != null && bottomTex != null)
// LODGeomap.calculateTangent(new Vector3f[]{rootPoint.mult(scale),rightPoint.mult(scale),bottomPoint.mult(scale)}, new Vector2f[]{rootTex,rightTex,bottomTex}, tangent, binormal);
normal . set ( n1 . add ( n2 ) . add ( n3 ) . add ( n4 ) . normalize ( ) ) ;
tangent . set ( normal . cross ( new Vector3f ( 0 , 0 , 1 ) ) . normalize ( ) ) ;
binormal . set ( new Vector3f ( 1 , 0 , 0 ) . cross ( normal ) . normalize ( ) ) ;
}
@ -592,11 +593,11 @@ public class TerrainPatch extends Geometry {
. crossLocal ( secondPoint . subtract ( rootPoint ) ) . normalizeLocal ( ) ;
return normal ;
}
protected Vector3f getMeshNormal ( int x , int z ) {
if ( x > = size | | z > = size )
return null ; // out of range
int index = ( z * size + x ) * 3 ;
FloatBuffer nb = ( FloatBuffer ) this . getMesh ( ) . getBuffer ( Type . Normal ) . getData ( ) ;
Vector3f normal = new Vector3f ( ) ;
@ -609,7 +610,7 @@ public class TerrainPatch extends Geometry {
protected float getHeight ( int x , int z , float xm , float zm ) {
return geomap . getHeight ( x , z , xm , zm ) ;
}
/ * *
* Locks the mesh ( sets it static ) to improve performance .
* But it it not editable then . Set unlock to make it editable .
@ -626,7 +627,7 @@ public class TerrainPatch extends Geometry {
public void unlockMesh ( ) {
getMesh ( ) . setDynamic ( ) ;
}
/ * *
* Returns the offset amount this terrain patch uses for textures .
*
@ -797,7 +798,7 @@ public class TerrainPatch extends Geometry {
protected void setLodBottom ( int lodBottom ) {
this . lodBottom = lodBottom ;
}
/ * public void setLodCalculator ( LodCalculatorFactory lodCalculatorFactory ) {
this . lodCalculatorFactory = lodCalculatorFactory ;
setLodCalculator ( lodCalculatorFactory . createCalculator ( this ) ) ;
@ -812,7 +813,7 @@ public class TerrainPatch extends Geometry {
if ( other instanceof BoundingVolume )
if ( ! getWorldBound ( ) . intersects ( ( BoundingVolume ) other ) )
return 0 ;
if ( other instanceof Ray )
return collideWithRay ( ( Ray ) other , results ) ;
else if ( other instanceof BoundingVolume )
@ -853,7 +854,7 @@ public class TerrainPatch extends Geometry {
* This most definitely is not optimized .
* /
private int collideWithBoundingBox ( BoundingBox bbox , CollisionResults results ) {
// test the four corners, for cases where the bbox dimensions are less than the terrain grid size, which is probably most of the time
Vector3f topLeft = worldCoordinateToLocal ( new Vector3f ( bbox . getCenter ( ) . x - bbox . getXExtent ( ) , 0 , bbox . getCenter ( ) . z - bbox . getZExtent ( ) ) ) ;
Vector3f topRight = worldCoordinateToLocal ( new Vector3f ( bbox . getCenter ( ) . x + bbox . getXExtent ( ) , 0 , bbox . getCenter ( ) . z - bbox . getZExtent ( ) ) ) ;
@ -872,11 +873,11 @@ public class TerrainPatch extends Geometry {
t = getTriangle ( bottomRight . x , bottomRight . z ) ;
if ( t ! = null & & bbox . collideWith ( t , results ) > 0 )
return 1 ;
// box is larger than the points on the terrain, so test against the points
for ( float z = topLeft . z ; z < bottomLeft . z ; z + = 1 ) {
for ( float x = topLeft . x ; x < topRight . x ; x + = 1 ) {
if ( x < 0 | | z < 0 | | x > = size | | z > = size )
continue ;
t = getTriangle ( x , z ) ;
@ -895,7 +896,7 @@ public class TerrainPatch extends Geometry {
// this reduces the save size to 10% by not saving the mesh
Mesh temp = getMesh ( ) ;
mesh = null ;
super . write ( ex ) ;
OutputCapsule oc = ex . getCapsule ( this ) ;
oc . write ( size , "size" , 16 ) ;
@ -908,7 +909,7 @@ public class TerrainPatch extends Geometry {
//oc.write(lodCalculatorFactory, "lodCalculatorFactory", null);
oc . write ( lodEntropy , "lodEntropy" , null ) ;
oc . write ( geomap , "geomap" , null ) ;
setMesh ( temp ) ;
}
@ -927,7 +928,7 @@ public class TerrainPatch extends Geometry {
//lodCalculatorFactory = (LodCalculatorFactory) ic.readSavable("lodCalculatorFactory", null);
lodEntropy = ic . readFloatArray ( "lodEntropy" , null ) ;
geomap = ( LODGeomap ) ic . readSavable ( "geomap" , null ) ;
Mesh regen = geomap . createMesh ( stepScale , new Vector2f ( 1 , 1 ) , offset , offsetAmount , totalSize , false ) ;
setMesh ( regen ) ;
//TangentBinormalGenerator.generate(this); // note that this will be removed
@ -955,6 +956,33 @@ public class TerrainPatch extends Geometry {
return clone ;
}
/ * *
* Called internally by com . jme3 . util . clone . Cloner . Do not call directly .
* /
@Override
public void cloneFields ( Cloner cloner , Object original ) {
this . stepScale = cloner . clone ( stepScale ) ;
this . offset = cloner . clone ( offset ) ;
this . leftNeighbour = null ;
this . topNeighbour = null ;
this . rightNeighbour = null ;
this . bottomNeighbour = null ;
// Don't feel like making geomap cloneable tonight
// so I'll copy the old logic.
this . geomap = new LODGeomap ( size , geomap . getHeightArray ( ) ) ;
Mesh m = geomap . createMesh ( stepScale , Vector2f . UNIT_XY , offset , offsetAmount , totalSize , false ) ;
this . setMesh ( m ) ;
// In this case, we always clone material even if the cloner is setup
// not to clone it. Terrain uses mutable textures and stuff so it's important
// to clone it. (At least that's my understanding and is evidenced by the old
// clone code specifically cloning material.) -pspeed
this . material = material . clone ( ) ;
}
protected void ensurePositiveVolumeBBox ( ) {
if ( getModelBound ( ) instanceof BoundingBox ) {
if ( ( ( BoundingBox ) getModelBound ( ) ) . getYExtent ( ) < 0 . 001f ) {