/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* 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 the 'Xith3D Project Group' 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) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.terrain.legacy;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import org.openmali.vecmath2.Tuple3f;
import org.openmali.vecmath2.Vector3f;
import org.xith3d.io.Archive;
import org.xith3d.io.InvalidFormat;
import org.xith3d.io.UnscribableNodeEncountered;
import org.xith3d.utility.logging.X3DLog;
/**
* A terrain class.
*
* @author David Yazel
*/
public class Terrain implements GroundHeightInterface
{
static float DetailThreshold = 100;
static final float VERTICAL_SCALE = 1.0f;
static LinkedList< TerrainCornerData > corners = new LinkedList< TerrainCornerData >();
static Vector3f SunVector = new Vector3f( 0.0705f, -0.9875f, -0.1411f ); // For demo lighting. Pick some unit vector pointing roughly downward.
static int BlockDeleteCount = 0; //xxxxx
static int BlockUpdateCount = 0; //xxxxx
TerrainDataBank[] banks;
// banks for high and low quality data. The low quality data is sampled at a much lower
// rate. We can mix banks to achive high performance
TerrainDataBank[] banksHigh;
TerrainDataBank[] banksLow;
int maxBanks;
int maxLevel;
int bankLevel;
int highSsample;
int lowSample;
TerrainCornerData rootData;
TerrainSquareHandle root;
Archive pagingFile;
public Terrain( int maxLevel, int bankLevel )
{
this.maxLevel = maxLevel;
this.maxBanks = ( 2 << maxLevel ) / ( 2 << bankLevel );
System.out.println( "World size is " + ( 2 << maxLevel ) );
System.out.println( "bank size is " + ( 2 << bankLevel ) );
System.out.println( "total number of banks is " + maxBanks + " in two directions" );
this.bankLevel = bankLevel;
banks = new TerrainDataBank[ maxBanks * maxBanks + 1 ];
for ( int i = 0; i < maxBanks * maxBanks; i++ )
{
banks[ i ] = new TerrainDataBank( i, 10000 );
}
banks[ maxBanks * maxBanks ] = new TerrainDataBank( maxBanks * maxBanks, 1000 );
rootData = new TerrainCornerData();
rootData.level = maxLevel;
root = newSquare( rootData );
rootData.square = root;
}
public int getWidth()
{
return ( 2 << maxLevel );
}
public int getDepth()
{
return ( 2 << maxLevel );
}
public void load( String filename ) throws IOException
{
pagingFile = new Archive( filename, true );
for ( int i = 0; i <= maxBanks * maxBanks; i++ )
{
try
{
TerrainDataBank b = (TerrainDataBank)pagingFile.read( "BANK_HIGH_" + i );
banks[ i ].newBank = b;
banks[ i ] = b;
}
catch ( IOException e )
{
X3DLog.print( e );
throw new IOException( "cannot read terrain banks" );
}
catch ( InvalidFormat invalidFormat )
{
X3DLog.print( invalidFormat );
throw new IOException( "cannot read terrain banks" );
}
}
this.resetTree( root );
this.recomputeError();
}
public void open( String filename )
{
}
/**
* Rebuilds the banks by getting rid of the deleted nodes and shifting all
* the nodes towards the front of the bank. Very useful after a call to CullStatic
*/
public void compressBanks()
{
for ( int i = 0; i < maxBanks * maxBanks; i++ )
banks[ i ] = TerrainDataBank.compress( banks[ i ] );
}
public void printBankUsage()
{
for ( int i = 0; i < maxBanks; i++ )
for ( int j = 0; j < maxBanks; j++ )
System.out.println( "Bank [" + i + "][" + j + "] = " + banks[ i * maxBanks + j ].freeList );
System.out.println( "Master Bank = " + banks[ maxBanks * maxBanks ].freeList );
}
private int getBank( TerrainCornerData pcd )
{
// ok now we need to determine what bank this goes in
if ( pcd.level > bankLevel )
{
return ( maxBanks * maxBanks );
}
int x = pcd.xorg / ( 2 << bankLevel );
int z = pcd.zorg / ( 2 << bankLevel );
if ( ( x >= maxBanks ) || ( z >= maxBanks ) )
throw new Error( "attempt to get coord outside banks " + pcd.xorg + "," + pcd.zorg );
return ( x * maxBanks + z );
}
private TerrainSquareHandle newSquare( TerrainCornerData pcd )
{
// ok now we need to determine what bank this goes in
TerrainSquareHandle s = new TerrainSquareHandle();
s.bank = getBank( pcd );
try
{
s.node = banks[ s.bank ].allocateNode();
}
catch ( Throwable t )
{
banks[ s.bank ] = TerrainDataBank.expand( banks[ s.bank ] );
s.node = banks[ s.bank ].allocateNode();
//Log.print( "Expanded " + s.bank + "for level " + pcd.level + " to " + banks[ s.bank ].maxNodes );
//System.exit( 0 );
}
s.b = banks[ s.bank ];
pcd.square = s;
/*
* Set static to true if/when this node contains real data, and
* not just interpolated values. When static == false, a node
* can be deleted by the Update() function if none of its
* vertices or children are enabled.
*/
s.setStatic( false );
int i;
for ( i = 0; i < 4; i++ )
{
s.setChild( i, -1 );
}
s.setEnabledFlags( (byte)0 );
for ( i = 0; i < 2; i++ )
{
s.setSubEnabledCount( i, (byte)0 );
}
// Set default vertex positions by interpolating from given corners.
// Just bilinear interpolation.
s.setY( 0, 0.25f * ( pcd.y[ 0 ] + pcd.y[ 1 ] + pcd.y[ 2 ] + pcd.y[ 3 ] ) );
s.setY( 1, 0.5f * ( pcd.y[ 3 ] + pcd.y[ 0 ] ) );
s.setY( 2, 0.5f * ( pcd.y[ 0 ] + pcd.y[ 1 ] ) );
s.setY( 3, 0.5f * ( pcd.y[ 1 ] + pcd.y[ 2 ] ) );
s.setY( 4, 0.5f * ( pcd.y[ 2 ] + pcd.y[ 3 ] ) );
for ( i = 0; i < 2; i++ )
{
s.setError( i, (short)0 );
}
for ( i = 0; i < 4; i++ )
{
s.setError( i + 2, (short)( Math.abs( ( s.getY( 0 ) + pcd.y[ i ] ) - ( s.getY( i + 1 ) + s.getY( ( ( i + 1 ) & 3 ) + 1 ) ) ) * 0.25f ) );
}
// Compute MinY/MaxY based on corner verts.
s.setMinY( (short)pcd.y[ 0 ] );
s.setMaxY( (short)pcd.y[ 0 ] );
for ( i = 1; i < 4; i++ )
{
float y = pcd.y[ i ];
if ( y < s.getMinY() )
{
s.setMinY( (short)y );
}
if ( y > s.getMaxY() )
{
s.setMaxY( (short)y );
}
}
return ( s );
}
private synchronized static TerrainCornerData getTerrainCornerData()
{
// return ( new TerrainCornerData() );
if ( corners.isEmpty() )
{
return ( new TerrainCornerData() );
}
TerrainCornerData qd = corners.removeFirst();
qd.init();
return ( qd );
}
public synchronized static void releaseCorner( TerrainCornerData o )
{
corners.add( o );
}
// Fills the given structure with the appropriate corner values for the
// specified child block, given our own vertex data and our corner
// vertex data from cd.
//
// ChildIndex mapping:
// +-+-+
// |1|0|
// +-+-+
// |2|3|
// +-+-+
//
// Verts mapping:
// 1-0
// | |
// 2-3
//
// vertex mapping:
// +-2-+
// | | |
// 3-0-1
// | | |
// +-4-+
private void SetupCornerData( TerrainCornerData q, TerrainCornerData cd, int childIndex )
{
int half = 1 << cd.level;
q.parent = cd;
q.square = new TerrainSquareHandle();
q.square.node = cd.square.getChild( childIndex );
q.square.bank = cd.square.getChildBank( childIndex );
if ( q.square.bank >= 0 )
q.square.b = banks[ q.square.bank ];
q.level = cd.level - 1;
q.childIndex = childIndex;
switch ( childIndex )
{
default:
case 0:
q.xorg = cd.xorg + half;
q.zorg = cd.zorg;
q.y[ 0 ] = cd.y[ 0 ];
q.y[ 1 ] = cd.square.getY( 2 );
q.y[ 2 ] = cd.square.getY( 0 );
q.y[ 3 ] = cd.square.getY( 1 );
break;
case 1:
q.xorg = cd.xorg;
q.zorg = cd.zorg;
q.y[ 0 ] = cd.square.getY( 2 );
q.y[ 1 ] = cd.y[ 1 ];
q.y[ 2 ] = cd.square.getY( 3 );
q.y[ 3 ] = cd.square.getY( 0 );
break;
case 2:
q.xorg = cd.xorg;
q.zorg = cd.zorg + half;
q.y[ 0 ] = cd.square.getY( 0 );
q.y[ 1 ] = cd.square.getY( 3 );
q.y[ 2 ] = cd.y[ 2 ];
q.y[ 3 ] = cd.square.getY( 4 );
break;
case 3:
q.xorg = cd.xorg + half;
q.zorg = cd.zorg + half;
q.y[ 0 ] = cd.square.getY( 1 );
q.y[ 1 ] = cd.square.getY( 0 );
q.y[ 2 ] = cd.square.getY( 4 );
q.y[ 3 ] = cd.y[ 3 ];
break;
}
}
/**
* Sets this node's static flag to true. If static == true, then the
* node or its children is considered to contain significant height data
* and shouldn't be deleted.
*
* @param cd
*/
void SetStatic( final TerrainCornerData cd )
{
if ( cd.square.getStatic() == false )
{
cd.square.setStatic( true );
// Propagate static status to ancestor nodes.
if ( ( cd.parent != null ) && ( cd.parent.square != null ) )
{
SetStatic( cd.parent );
}
}
}
public int CountNodes()
{
return ( CountNodes( root ) );
}
/**
* Debugging function. Counts the number of nodes in this subtree.
*
* @param sq
* @return the number of nodes
*/
public int CountNodes( TerrainSquareHandle sq )
{
int count = 1; // Count ourself.
// Count descendants.
for ( int i = 0; i < 4; i++ )
{
TerrainSquareHandle c = getChild( sq, i );
if ( c != null )
{
count += CountNodes( c );
}
}
return ( count );
}
public float getY( float x, float z )
{
return ( getHeight( rootData, x, z, false ) );
}
public float getCurY( float x, float z )
{
return ( getHeight( rootData, x, z, false ) );
}
/**
* @param cd
* @param x
* @param z
* @param enabledOnly
* @return the height of the heightfield at the specified x,z coordinates.
*/
float getHeight( final TerrainCornerData cd, float x, float z, boolean enabledOnly )
{
final int half = 1 << cd.level;
float lx = ( x - cd.xorg ) / (float)half;
float lz = ( z - cd.zorg ) / (float)half;
int ix = (int)Math.floor( lx );
int iz = (int)Math.floor( lz );
// Clamp.
if ( ix < 0 )
{
ix = 0;
}
if ( ix > 1 )
{
ix = 1;
}
if ( iz < 0 )
{
iz = 0;
}
if ( iz > 1 )
{
iz = 1;
}
int index = ix ^ ( ( iz ^ 1 ) + ( iz << 1 ) );
TerrainSquareHandle c = getChild( cd, index );
if ( c != null && ( !enabledOnly || ( c.getEnabledFlags() & ( 16 << index ) ) != 0 ) )
if ( ( c != null ) && c.getStatic() )
{
// Pass the query down to the child which contains it.
TerrainCornerData q = getTerrainCornerData();
SetupCornerData( q, cd, index );
float height = getHeight( q, x, z, enabledOnly );
releaseCorner( q );
return ( height );
}
// Bilinear interpolation.
lx -= ix;
if ( lx < 0 )
{
lx = 0;
}
if ( lx > 1 )
{
lx = 1;
}
lz -= iz;
if ( lx < 0 )
{
lz = 0;
}
if ( lz > 1 )
{
lz = 1;
}
float s00;
float s01;
float s10;
float s11;
switch ( index )
{
default:
case 0:
s00 = cd.square.getY( 2 );
s01 = cd.y[ 0 ];
s10 = cd.square.getY( 0 );
s11 = cd.square.getY( 1 );
break;
case 1:
s00 = cd.y[ 1 ];
s01 = cd.square.getY( 2 );
s10 = cd.square.getY( 3 );
s11 = cd.square.getY( 0 );
break;
case 2:
s00 = cd.square.getY( 3 );
s01 = cd.square.getY( 0 );
s10 = cd.y[ 2 ];
s11 = cd.square.getY( 4 );
break;
case 3:
s00 = cd.square.getY( 0 );
s01 = cd.square.getY( 1 );
s10 = cd.square.getY( 4 );
s11 = cd.y[ 3 ];
break;
}
return ( ( ( ( s00 * ( 1 - lx ) ) + ( s01 * lx ) ) * ( 1 - lz ) ) + ( ( ( s10 * ( 1 - lx ) ) + ( s11 * lx ) ) * lz ) );
}
// Traverses the tree in search of the quadsquare neighboring this square to the
// specified direction. 0-3 --> { E, N, W, S }.
// @return NULL if the neighbor is outside the bounds of the tree.
TerrainSquareHandle GetNeighbor( TerrainSquareHandle sq, int dir, final TerrainCornerData cd )
{
// If we don't have a parent, then we don't have a neighbor.
// (Actually, we could have inter-tree connectivity at this level
// for connecting separate trees together.)
if ( cd.parent == null )
{
return ( null );
}
// Find the parent and the child-index of the square we want to locate or create.
TerrainSquareHandle p = null;
int index = cd.childIndex ^ 1 ^ ( ( dir & 1 ) << 1 );
boolean SameParent = ( ( ( dir - cd.childIndex ) & 2 ) != 0 ) ? true : false;
if ( SameParent )
{
p = cd.parent.square;
}
else
{
p = GetNeighbor( cd.parent.square, dir, cd.parent );
if ( p == null )
{
return ( null );
}
}
TerrainSquareHandle n = getChild( p, index );
return ( n );
}
public float recomputeError()
{
return ( recomputeError( root, rootData ) );
}
/**
* Recomputes the error values for this tree.
* Also updates MinY & MaxY.
* Also computes quick & dirty vertex lighting for the demo.
*
* @return the max error.
*
* @param sq
* @param cd
*/
float recomputeError( TerrainSquareHandle sq, final TerrainCornerData cd )
{
int i;
// Measure error of center and edge vertices.
float maxerror = 0;
// Compute error of center vert.
float e;
if ( ( cd.childIndex & 1 ) != 0 )
{
e = Math.abs( sq.getY( 0 ) - ( ( cd.y[ 1 ] + cd.y[ 3 ] ) * 0.5f ) );
}
else
{
e = Math.abs( sq.getY( 0 ) - ( ( cd.y[ 0 ] + cd.y[ 2 ] ) * 0.5f ) );
}
if ( e > maxerror )
{
maxerror = e;
}
// Initial min/max.
short MaxY = (short)sq.getY( 0 );
short MinY = (short)sq.getY( 0 );
// Check min/max of corners.
for ( i = 0; i < 4; i++ )
{
float y = cd.y[ i ];
if ( y < MinY )
{
MinY = (short)y;
}
if ( y > MaxY )
{
MaxY = (short)y;
}
}
// Edge verts.
e = Math.abs( sq.getY( 1 ) - ( ( cd.y[ 0 ] + cd.y[ 3 ] ) * 0.5f ) );
if ( e > maxerror )
{
maxerror = e;
}
sq.setError( 0, (short)e );
e = Math.abs( sq.getY( 4 ) - ( ( cd.y[ 2 ] + cd.y[ 3 ] ) * 0.5f ) );
if ( e > maxerror )
{
maxerror = e;
}
sq.setError( 1, (short)e );
// Min/max of edge verts.
for ( i = 0; i < 4; i++ )
{
float y = sq.getY( 1 + i );
if ( y < MinY )
{
MinY = (short)y;
}
if ( y > MaxY )
{
MaxY = (short)y;
}
}
cd.square.setMinY( MinY );
cd.square.setMaxY( MaxY );
// Check child squares.
for ( i = 0; i < 4; i++ )
{
TerrainCornerData q = new TerrainCornerData();
TerrainSquareHandle c = getChild( cd, i );
if ( c != null )
{
SetupCornerData( q, cd, i );
sq.setError( i + 2, (short)recomputeError( q.square, q ) );
if ( q.square.getMinY() < MinY )
{
MinY = q.square.getMinY();
}
if ( q.square.getMaxY() > MaxY )
{
MaxY = q.square.getMaxY();
}
}
else
{
// Compute difference between bilinear average at child center, and diagonal edge approximation.
sq.setError( i + 2, (short)( Math.abs( ( sq.getY( 0 ) + cd.y[ i ] ) - ( sq.getY( i + 1 ) + sq.getY( ( ( i + 1 ) & 3 ) + 1 ) ) ) * 0.25f ) );
}
if ( sq.getError( i + 2 ) > maxerror )
{
maxerror = sq.getError( i + 2 );
}
}
// The error, MinY/MaxY, and lighting values for this node and descendants are correct now.
cd.square.setDirty( false );
cd.square.setMinY( MinY );
cd.square.setMaxY( MaxY );
return ( maxerror );
}
/**
* Clears all enabled flags, and delete all non-static child nodes.
*
* @param sq
*/
void resetTree( TerrainSquareHandle sq )
{
int i;
for ( i = 0; i < 4; i++ )
{
TerrainSquareHandle c = getChild( sq, i );
if ( c != null )
{
resetTree( c );
if ( c.getStatic() == false )
{
//delete Child[ i ];
c.delete();
sq.setChild( i, -1 );
sq.setChildBank( i, -1 );
}
}
}
sq.setEnabledFlags( (byte)0 );
sq.setSubEnabledCount( 0, (byte)0 );
sq.setSubEnabledCount( 1, (byte)0 );
sq.setDirty( true );
}
public void cullStaticData( float threshold, int maxLevelToCull )
{
staticCullData( root, rootData, threshold, maxLevelToCull );
}
/**
* Examine the tree and remove nodes which don't contain necessary
* detail. Necessary detail is defined as vertex data with a
* edge-length to height ratio less than ThresholdDetail.
*
* @param sq
* @param cd
* @param thresholdDetail
* @param maxLevelToCull
*/
void staticCullData( TerrainSquareHandle sq, final TerrainCornerData cd, float thresholdDetail, int maxLevelToCull )
{
// First, clean non-static nodes out of the tree.
resetTree( sq );
// Make sure error values are up-to-date.
if ( sq.getDirty() )
{
recomputeError( sq, cd );
}
// Recursively check all the nodes and do necessary removal.
// We must start at the bottom of the tree, and do one level of
// the tree at a time, to ensure the dependencies are accounted
// for properly.
int level;
for ( level = 0; level < maxLevelToCull; level++ )
{
staticCullAux( sq, cd, thresholdDetail, level );
}
}
/**
* Check this node and its descendents, and remove nodes which don't contain
* necessary detail.
*
* @param sq
* @param cd
* @param TargetLevel
*/
void deleteStaticData( TerrainSquareHandle sq, final TerrainCornerData cd, int TargetLevel )
{
int i;
int j;
if ( cd.level > TargetLevel )
{
TerrainCornerData q = new TerrainCornerData();
// Just recurse to child nodes.
for ( j = 0; j < 4; j++ )
{
if ( j < 2 )
{
i = 1 - j;
}
else
{
i = j;
}
TerrainSquareHandle c = getChild( sq, i );
if ( c != null )
{
SetupCornerData( q, cd, i );
deleteStaticData( c, q, TargetLevel );
}
}
return;
}
// See if we have child nodes.
boolean StaticChildren = false;
for ( i = 0; i < 4; i++ )
{
if ( getChild( sq, i ) != null )
{
StaticChildren = true;
if ( getChild( sq, i ).getDirty() )
{
sq.setDirty( true );
}
}
}
/*
if (StaticChildren)
Log.print( "we have static children" );
else
Log.print( "we do not have static children" );
Log.print( "after verts are " + sq.getY( 0 ) + "," + sq.getY( 1 ) + "," + sq.getY( 2 ) + "," + sq.getY( 3 ) + "," + sq.getY( 4 ) );
*/
// If we have no children and no necessary edges, then see if we can delete ourself.
if ( ( StaticChildren == false ) && ( cd.parent != null ) )
{
//delete cd.parent.Square.Child[ cd.ChildIndex ]; // Delete this.
TerrainSquareHandle c = getChild( cd.parent.square, cd.childIndex );
c.delete();
cd.parent.square.setChild( cd.childIndex, -1 );
cd.parent.square.setChildBank( cd.childIndex, -1 );
}
}
// Check this node and its descendents, and remove nodes which don't contain
// necessary detail.
void staticCullAux( TerrainSquareHandle sq, final TerrainCornerData cd, float thresholdDetail, int targetLevel )
{
int i;
int j;
if ( cd.level > targetLevel )
{
TerrainCornerData q = new TerrainCornerData();
// Just recurse to child nodes.
for ( j = 0; j < 4; j++ )
{
if ( j < 2 )
{
i = 1 - j;
}
else
{
i = j;
}
TerrainSquareHandle c = getChild( sq, i );
if ( c != null )
{
SetupCornerData( q, cd, i );
staticCullAux( c, q, thresholdDetail, targetLevel );
}
}
return;
}
//Log.print( "we are checking node :" + cd.xorg + "," + cd.zorg + " with height " + sq.getY( 3 ) );
// We're at the target level. Check this node to see if it's OK to delete it.
// Check edge vertices to see if they're necessary.
float size = 2 << cd.level; // Edge length.
//Log.print( "after verts are " + sq.getY( 0 ) + "," + sq.getY( 1 ) + "," + sq.getY( 2 ) + "," + sq.getY( 3 ) + "," + sq.getY( 4 ) );
//Log.print( "error 0 is " + sq.getError( 0 ) );
if ( ( sq.getChild( 0 ) == -1 ) && ( sq.getChild( 3 ) == -1 ) && ( ( sq.getError( 0 ) * thresholdDetail ) < size ) )
{
TerrainSquareHandle s = GetNeighbor( sq, 0, cd );
//if (s != null)
//if ((s.bank == sq.bank) && (s.node == sq.node)) throw new Error( "neighbor is same as self" ) );
if ( ( s == null ) || ( ( s.getChild( 1 ) == -1 ) && ( s.getChild( 2 ) == -1 ) ) )
{
// Force vertex height to the edge value.
float y = ( cd.y[ 0 ] + cd.y[ 3 ] ) * 0.5f;
sq.setY( 1, y );
sq.setError( 0, (short)0 );
//Log.print( "setting Y[1] to " + y );
// Force alias vertex to match.
if ( s != null )
{
s.setY( 3, y );
//Log.print( "setting neghbor Y[3] to " + y );
}
sq.setDirty( true );
}
}
//Log.print( "error 1 is " + sq.getError( 1 ) );
if ( ( sq.getChild( 2 ) == -1 ) && ( sq.getChild( 3 ) == -1 ) && ( ( sq.getError( 1 ) * thresholdDetail ) < size ) )
{
TerrainSquareHandle s = GetNeighbor( sq, 3, cd );
//if (s != null)
//if ((s.bank == sq.bank) && (s.node == sq.node)) throw new Error( "neighbor is same as self" ) );
if ( ( s == null ) || ( ( s.getChild( 0 ) == -1 ) && ( s.getChild( 1 ) == -1 ) ) )
{
float y = ( cd.y[ 2 ] + cd.y[ 3 ] ) * 0.5f;
//Log.print( "setting Y[4] to " + y );
sq.setY( 4, y );
sq.setError( 1, (short)0 );
if ( s != null )
{
//Log.print( "setting neghbor Y[2] to " + y );
s.setY( 2, y );
}
sq.setDirty( true );
}
}
// See if we have child nodes.
boolean staticChildren = false;
for ( i = 0; i < 4; i++ )
{
if ( getChild( sq, i ) != null )
{
staticChildren = true;
if ( getChild( sq, i ).getDirty() )
{
sq.setDirty( true );
}
}
}
/*
if (StaticChildren)
Log.print( "we have static children" );
else
Log.print( "we do not have static children" );
Log.print( "after verts are " + sq.getY( 0 ) + "," + sq.getY( 1 ) + "," + sq.getY( 2 ) + "," + sq.getY( 3 ) + "," + sq.getY( 4 ) );
*/
// If we have no children and no necessary edges, then see if we can delete ourself.
if ( ( staticChildren == false ) && ( cd.parent != null ) )
{
boolean necessaryEdges = false;
for ( i = 0; i < 4; i++ )
{
// See if vertex deviates from edge between corners.
//Log.print( " sub values are " + sq.getY( i + 1 ) + ", " + cd.y[ i ] + ", " + cd.y[ (i + 3) & 3 ] );
// See if vertex deviates from edge between corners.
float diff = Math.abs( sq.getY( i + 1 ) - ( ( cd.y[ i ] + cd.y[ ( i + 3 ) & 3 ] ) * 0.5f ) );
//Log.print("diff = "+diff);
if ( diff > 0.00001f )
{
necessaryEdges = true;
}
}
if ( !necessaryEdges )
{
//Log.print( "no necessary edges" );
size *= 1.414213562f; // sqrt( 2 ), because diagonal is longer than side.
//Log.print( "checking with size " + size );
//Log.print( "tested against " + cd.parent.square.getError( 2 + cd.childIndex ) * ThresholdDetail );
if ( ( cd.parent.square.getError( 2 + cd.childIndex ) * thresholdDetail ) < size )
{
//delete cd.parent.Square.Child[ cd.ChildIndex ]; // Delete this.
TerrainSquareHandle c = getChild( cd.parent.square, cd.childIndex );
c.delete();
cd.parent.square.setChild( cd.childIndex, -1 );
cd.parent.square.setChildBank( cd.childIndex, -1 );
//System.out.println( "Deleting node" );
}
//throw new Error( "done" ) );
}
//else Log.print( "we have necessary edges" );
}
}
// Enable the specified edge vertex. Indices go { e, n, w, s }.
// Increments the appropriate reference-count if IncrementCount is true.
void enableEdgeVertex( TerrainSquareHandle sq, int index, boolean incrementCount, final TerrainCornerData cd )
{
if ( ( ( sq.getEnabledFlags() & ( 1 << index ) ) != 0 ) && !incrementCount )
{
return;
}
// Turn on flag and deal with reference count.
sq.setEnabledFlags( (byte)( sq.getEnabledFlags() | ( 1 << index ) ) );
if ( ( incrementCount == true ) && ( ( index == 0 ) || ( index == 3 ) ) )
{
sq.incSubEnabledCount( index & 1 );
}
/*
* Now we need to enable the opposite edge vertex of the adjacent square (i.e. the alias vertex).
* This is a little tricky, since the desired neighbor node may not exist, in which
* case we have to create it, in order to prevent cracks. Creating it may in turn cause
* further edge vertices to be enabled, propagating updates through the tree.
* The sticking point is the TerrainCornerData list, which
* conceptually is just a linked list of activation structures.
* In this function, however, we will introduce branching into
* the "list", making it in actuality a tree. This is all kind
* of obscure and hard to explain in words, but basically what
* it means is that our implementation has to be properly
* recursive.
* Travel upwards through the tree, looking for the parent in common with our desired neighbor.
* Remember the path through the tree, so we can travel down the complementary path to get to the neighbor.
*/
TerrainSquareHandle p = cd.square;
TerrainCornerData pcd = cd;
int ct = 0;
int[] stack = new int[ 32 ];
for ( ;; )
{
int ci = pcd.childIndex;
if ( ( pcd.parent == null ) || ( pcd.parent.square == null ) )
{
// Neighbor doesn't exist (it's outside the tree), so there's no alias vertex to enable.
return;
}
p = pcd.parent.square;
pcd = pcd.parent;
boolean SameParent = ( ( ( index - ci ) & 2 ) != 0 ) ? true : false;
ci = ci ^ 1 ^ ( ( index & 1 ) << 1 ); // Child index of neighbor node.
stack[ ct ] = ci;
ct++;
if ( SameParent )
{
break;
}
}
// Get a pointer to our neighbor (create if necessary), by walking down
// the quadtree from our shared ancestor.
p = EnableDescendant( p, ct, stack, pcd );
if ( p == null )
throw new Error( "enabled descendant is null!" );
// Finally: enable the vertex on the opposite edge of our neighbor, the alias of the original vertex.
index ^= 2;
p.setEnabledFlags( (byte)( p.getEnabledFlags() | ( 1 << index ) ) );
if ( ( incrementCount == true ) && ( ( index == 0 ) || ( index == 3 ) ) )
{
p.setSubEnabledCount( index & 1, (byte)( p.getSubEnabledCount( index & 1 ) + 1 ) );
}
}
TerrainSquareHandle getChild( TerrainCornerData cd, int index )
{
if ( cd.square.getChild( index ) == -1 )
return ( null );
TerrainSquareHandle s = new TerrainSquareHandle();
s.node = cd.square.getChild( index );
s.bank = cd.square.getChildBank( index );
s.b = banks[ s.bank ];
return ( s );
}
TerrainSquareHandle getChild( TerrainSquareHandle s, int index )
{
if ( s.getChild( index ) == -1 )
return ( null );
TerrainSquareHandle ss = new TerrainSquareHandle();
ss.node = s.getChild( index );
ss.bank = s.getChildBank( index );
ss.b = banks[ ss.bank ];
return ( ss );
}
// This function enables the descendant node 'count' generations below
// us, located by following the list of child indices in path[].
// Creates the node if necessary, and returns a pointer to it.
TerrainSquareHandle EnableDescendant( TerrainSquareHandle sq, int count, int[] path, final TerrainCornerData cd )
{
count--;
int ChildIndex = path[ count ];
if ( ( cd.square.getEnabledFlags() & ( 16 << ChildIndex ) ) == 0 )
{
EnableChild( sq, ChildIndex, cd );
}
if ( count <= 0 )
return ( getChild( cd, ChildIndex ) );
TerrainCornerData q = new TerrainCornerData();
SetupCornerData( q, cd, ChildIndex );
TerrainSquareHandle qs = EnableDescendant( sq, count, path, q );
return ( qs );
}
/**
* Creates a child square at the specified index.
*
* @param sq
* @param index
* @param cd
*/
void CreateChild( TerrainSquareHandle sq, int index, final TerrainCornerData cd )
{
if ( cd.square.getChild( index ) == -1 )
{
TerrainCornerData q = new TerrainCornerData();
SetupCornerData( q, cd, index );
TerrainSquareHandle h = newSquare( q );
cd.square.setChild( index, h.node );
cd.square.setChildBank( index, h.bank );
}
}
/**
* Enables the indexed child node. { ne, nw, sw, se }
* Causes dependent edge vertices to be enabled.
*
* @param sq
* @param index
* @param cd
*/
void EnableChild( TerrainSquareHandle sq, int index, final TerrainCornerData cd )
{
//if (Enabled[index + 4] == false) {
if ( ( cd.square.getEnabledFlags() & ( 16 << index ) ) == 0 )
{
//Enabled[ index + 4 ] = true;
cd.square.setEnabledFlags( (byte)( cd.square.getEnabledFlags() | ( 16 << index ) ) );
enableEdgeVertex( sq, index, true, cd );
enableEdgeVertex( sq, ( index + 1 ) & 3, true, cd );
if ( getChild( cd, index ) == null )
{
CreateChild( sq, index, cd );
}
}
}
void NotifyChildDisable( TerrainSquareHandle sq, final TerrainCornerData cd, int index )
{
// Clear enabled flag for the child.
cd.square.setEnabledFlags( (byte)( cd.square.getEnabledFlags() & ~( 16 << index ) ) );
// Update child enabled counts for the affected edge verts.
TerrainSquareHandle s;
if ( ( index & 2 ) != 0 )
{
s = cd.square;
}
else
{
s = GetNeighbor( sq, 1, cd );
}
if ( s != null )
{
s.setSubEnabledCount( 1, (byte)( s.getSubEnabledCount( 1 ) - 1 ) );
}
if ( ( index == 1 ) || ( index == 2 ) )
{
s = GetNeighbor( sq, 2, cd );
}
else
{
s = cd.square;
}
if ( s != null )
{
s.setSubEnabledCount( 0, (byte)( s.getSubEnabledCount( 0 ) - 1 ) );
}
TerrainSquareHandle c = getChild( sq, index );
if ( c.getStatic() == false )
{
//delete Child[ index ];
c.delete();
sq.setChild( index, -1 );
sq.setChildBank( index, -1 );
BlockDeleteCount++; //xxxxx
}
}
/**
* @return true if the vertex at (x,z) with the given world-space error between
* its interpolated location and its true location, should be enabled, given that
* the viewpoint is located at Viewer[].
*/
static boolean vertexTest( float x, float y, float z, float error, final float[] Viewer )
{
final float dx = Math.abs( x - Viewer[ 0 ] );
final float dy = Math.abs( y - Viewer[ 1 ] );
final float dz = Math.abs( z - Viewer[ 2 ] );
float d = dx;
if ( dy > d )
{
d = dy;
}
if ( dz > d )
{
d = dz;
}
return ( ( error * DetailThreshold ) > d );
}
/**
* @return true if any vertex within the specified box (origin at x,z,
* edges of length size) with the given error value could be enabled
* based on the given viewer location.
*/
static boolean BoxTest( float x, float z, float size, float miny, float maxy, float error, final float[] Viewer )
{
// Find the minimum distance to the box.
final float half = size * 0.5f;
final float dx = Math.abs( ( x + half ) - Viewer[ 0 ] ) - half;
final float dy = Math.abs( ( ( miny + maxy ) * 0.5f ) - Viewer[ 1 ] ) - ( ( maxy - miny ) * 0.5f );
final float dz = Math.abs( ( z + half ) - Viewer[ 2 ] ) - half;
float d = dx;
if ( dy > d )
{
d = dy;
}
if ( dz > d )
{
d = dz;
}
return ( ( error * DetailThreshold ) > d );
}
public void update( Tuple3f loc, float detail )
{
Update( root, rootData, new float[]
{
loc.getX(), loc.getY(), loc.getZ()
}, detail );
}
/**
* Refresh the vertex enabled states in the tree, according to the
* location of the viewer. May force creation or deletion of qsquares
* in areas which need to be interpolated.
*/
public void Update( TerrainSquareHandle sq, final TerrainCornerData cd, final float[] ViewerLocation, float Detail )
{
DetailThreshold = Detail * VERTICAL_SCALE;
UpdateAux( sq, cd, ViewerLocation, 0 );
}
/**
* Does the actual work of updating enabled states and tree growing/shrinking.
*
* @param sq
* @param cd
* @param ViewerLocation
* @param CenterError
*/
void UpdateAux( TerrainSquareHandle sq, final TerrainCornerData cd, final float[] ViewerLocation, float CenterError )
{
BlockUpdateCount++; //xxxxx
// Make sure error values are current.
if ( sq.getDirty() )
{
recomputeError( sq, cd );
}
final int half = 1 << cd.level;
final int whole = half << 1;
// See about enabling child verts.
if ( ( ( sq.getEnabledFlags() & 1 ) == 0 ) && ( vertexTest( cd.xorg + whole, sq.getY( 1 ), cd.zorg + half, sq.getError( 0 ), ViewerLocation ) == true ) )
{
enableEdgeVertex( sq, 0, false, cd ); // East vert.
}
if ( ( ( sq.getEnabledFlags() & 8 ) == 0 ) && ( vertexTest( cd.xorg + half, sq.getY( 4 ), cd.zorg + whole, sq.getError( 1 ), ViewerLocation ) == true ) )
{
enableEdgeVertex( sq, 3, false, cd ); // South vert.
}
if ( cd.level > 0 )
{
if ( ( sq.getEnabledFlags() & 32 ) == 0 )
{
if ( BoxTest( cd.xorg, cd.zorg, half, sq.getMinY(), sq.getMaxY(), sq.getError( 3 ), ViewerLocation ) == true )
{
EnableChild( sq, 1, cd ); // nw child.er
}
}
if ( ( sq.getEnabledFlags() & 16 ) == 0 )
{
if ( BoxTest( cd.xorg + half, cd.zorg, half, sq.getMinY(), sq.getMaxY(), sq.getError( 2 ), ViewerLocation ) == true )
{
EnableChild( sq, 0, cd ); // ne child.
}
}
if ( ( sq.getEnabledFlags() & 64 ) == 0 )
{
if ( BoxTest( cd.xorg, cd.zorg + half, half, sq.getMinY(), sq.getMaxY(), sq.getError( 4 ), ViewerLocation ) == true )
{
EnableChild( sq, 2, cd ); // sw child.
}
}
if ( ( sq.getEnabledFlags() & 128 ) == 0 )
{
if ( BoxTest( cd.xorg + half, cd.zorg + half, half, sq.getMinY(), sq.getMaxY(), sq.getError( 5 ), ViewerLocation ) == true )
{
EnableChild( sq, 3, cd ); // se child.
}
}
// Recurse into child quadrants as necessary.
TerrainCornerData q = getTerrainCornerData();
if ( ( sq.getEnabledFlags() & 32 ) != 0 )
{
SetupCornerData( q, cd, 1 );
UpdateAux( q.square, q, ViewerLocation, sq.getError( 3 ) );
}
if ( ( sq.getEnabledFlags() & 16 ) != 0 )
{
SetupCornerData( q, cd, 0 );
UpdateAux( q.square, q, ViewerLocation, sq.getError( 2 ) );
}
if ( ( sq.getEnabledFlags() & 64 ) != 0 )
{
SetupCornerData( q, cd, 2 );
UpdateAux( q.square, q, ViewerLocation, sq.getError( 4 ) );
}
if ( ( sq.getEnabledFlags() & 128 ) != 0 )
{
SetupCornerData( q, cd, 3 );
UpdateAux( q.square, q, ViewerLocation, sq.getError( 5 ) );
}
releaseCorner( q );
}
// Test for disabling. East, South, and center.
if ( ( ( sq.getEnabledFlags() & 1 ) != 0 ) && ( sq.getSubEnabledCount( 0 ) == 0 ) && ( vertexTest( cd.xorg + whole, sq.getY( 1 ), cd.zorg + half, sq.getError( 0 ), ViewerLocation ) == false ) )
{
sq.andEnabledFlags( (byte)~1 );
TerrainSquareHandle s = GetNeighbor( sq, 0, cd );
if ( s != null )
{
s.andEnabledFlags( (byte)~4 );
}
}
if ( ( ( sq.getEnabledFlags() & 8 ) != 0 ) && ( sq.getSubEnabledCount( 1 ) == 0 ) && ( vertexTest( cd.xorg + half, sq.getY( 4 ), cd.zorg + whole, sq.getError( 1 ), ViewerLocation ) == false ) )
{
sq.andEnabledFlags( (byte)~8 );
TerrainSquareHandle s = GetNeighbor( sq, 3, cd );
if ( s != null )
{
s.andEnabledFlags( (byte)~2 );
}
}
if ( ( sq.getEnabledFlags() == 0 ) && ( cd.parent != null ) && ( BoxTest( cd.xorg, cd.zorg, whole, sq.getMinY(), sq.getMaxY(), CenterError, ViewerLocation ) == false ) )
{
// Disable ourself.
NotifyChildDisable( cd.parent.square, cd.parent, cd.childIndex ); // nb: possibly deletes 'this'.
}
}
void clearHeightData( final TerrainCornerData cd, final TerrainSampleInterface hm )
{
}
public void addData( TerrainSampleInterface sample )
{
AddHeightMap( root, rootData, sample, 0 );
}
public void addData( TerrainSampleInterface sample, float minDetail )
{
AddHeightMap( root, rootData, sample, minDetail );
}
/**
* Sets the height of all samples within the specified rectangular
* region using the given array of floats. Extends the tree to the
* level of detail defined by (1 << hm.Scale) as necessary.
*/
void AddHeightMap( TerrainSquareHandle sq, final TerrainCornerData cd, final TerrainSampleInterface hm, float minDetail )
{
// If block is outside rectangle, then don't bother.
int BlockSize = 2 << cd.level;
if ( ( cd.xorg > ( hm.getXOrg() + ( ( hm.getXDim() + 2 ) << hm.getScale() ) ) ) || ( ( cd.xorg + BlockSize ) < ( hm.getXOrg() - ( 1 << hm.getScale() ) ) ) || ( cd.zorg > ( hm.getZOrg() + ( ( hm.getZDim() + 2 ) << hm.getScale() ) ) ) || ( ( cd.zorg + BlockSize ) < ( hm.getZOrg() - ( 1 << hm.getScale() ) ) ) )
{
// This square does not touch the given height array area; no need to modify this square or descendants.
return;
}
if ( ( cd.parent != null ) && ( cd.parent.square != null ) )
{
EnableChild( cd.parent.square, cd.childIndex, cd.parent ); // causes parent edge verts to be enabled, possibly causing neighbor blocks to be created.
}
int i;
int half = 1 << cd.level;
// Create and update child nodes.
for ( i = 0; i < 4; i++ )
{
TerrainCornerData q = new TerrainCornerData();
SetupCornerData( q, cd, i );
if ( ( sq.getChild( i ) == -1 ) && ( cd.level > hm.getScale() ) )
{
// Create child node w/ current (unmodified) values for corner verts.
CreateChild( sq, i, cd );
SetupCornerData( q, cd, i );
}
// Recurse.
if ( sq.getChild( i ) != -1 )
{
AddHeightMap( q.square, q, hm, minDetail );
if ( q.level == bankLevel )
{
if ( minDetail > 0 )
{
int bank = getBank( q );
X3DLog.printlnEx( "compressing bank ", bank );
staticCullData( getChild( sq, i ), q, minDetail, bankLevel );
banks[ bank ] = TerrainDataBank.compress( banks[ bank ] );
System.gc();
}
}
}
}
// Deviate vertex heights based on data sampled from heightmap.
float[] s = new float[ 5 ];
s[ 0 ] = hm.sample( cd.xorg + half, cd.zorg + half );
s[ 1 ] = hm.sample( cd.xorg + ( half * 2 ), cd.zorg + half );
s[ 2 ] = hm.sample( cd.xorg + half, cd.zorg );
s[ 3 ] = hm.sample( cd.xorg, cd.zorg + half );
s[ 4 ] = hm.sample( cd.xorg + half, cd.zorg + ( half * 2 ) );
// Modify the vertex heights if necessary, and set the dirty
// flag if any modifications occur, so that we know we need to
// recompute error data later.
for ( i = 0; i < 5; i++ )
{
//if (s[ i ] != 0) {
sq.setDirty( true );
//vertex[ i ].Y += s[ i ];
sq.setY( i, s[ i ] );
//}
}
if ( !sq.getDirty() )
{
// Check to see if any child nodes are dirty, and set the dirty flag if so.
for ( i = 0; i < 4; i++ )
{
if ( ( sq.getChild( i ) != -1 ) && getChild( sq, i ).getDirty() )
{
sq.setDirty( true );
break;
}
}
}
if ( sq.getDirty() )
{
SetStatic( cd );
}
}
public int render( TerrainRenderInterface r )
{
return ( render( rootData, r ) );
}
/**
* Draws the heightfield represented by this tree.
* @return the number of triangles rendered.
*/
int render( TerrainCornerData cd, TerrainRenderInterface r )
{
int n = renderAux( cd.square, cd, r );
return ( n );
}
/**
* Does the work of rendering this square. Uses the enabled vertices only.
* Recurses as necessary.
*/
int renderAux( final TerrainSquareHandle sq, final TerrainCornerData cd, TerrainRenderInterface r )
{
int half = 1 << cd.level;
int whole = 2 << cd.level;
//int blockSize = 2 << cd.level;
/*
if ((cd.xorg > r.ux) || ((cd.xorg + whole) < r.lx) || (cd.zorg > r.uz) || ((cd.zorg + whole) < r.lz))
{
// This square does not touch the given height array area; no need to modify this square or descendants.
return ( 0 );
}
*/
// If this square is outside the bounds of the render then ignore
int i;
int num = 0;
int flags = 0;
int mask = 1;
TerrainCornerData q = getTerrainCornerData();
for ( i = 0; i < 4; i++, mask <<= 1 )
{
if ( ( sq.getEnabledFlags() & ( 16 << i ) ) != 0 )
{
SetupCornerData( q, cd, i );
num += renderAux( q.square, q, r );
}
else
{
flags |= mask;
}
}
releaseCorner( q );
if ( flags == 0 )
{
return ( num );
}
r.start();
// // xxx debug color.
// glColor3f(cd.level * 10 / 255.0, ((cd.level & 3) * 60 + ((cd.zorg >> cd.level) & 255)) / 255.0, ((cd.level & 7) * 30 + ((cd.xorg >> cd.level) & 255)) / 255.0);
// Init vertex data.
r.initVert( 0, cd.xorg + half, sq.getY( 0 ), cd.zorg + half );
r.initVert( 1, cd.xorg + whole, sq.getY( 1 ), cd.zorg + half );
r.initVert( 2, cd.xorg + whole, cd.y[ 0 ], cd.zorg );
r.initVert( 3, cd.xorg + half, sq.getY( 2 ), cd.zorg );
r.initVert( 4, cd.xorg, cd.y[ 1 ], cd.zorg );
r.initVert( 5, cd.xorg, sq.getY( 3 ), cd.zorg + half );
r.initVert( 6, cd.xorg, cd.y[ 2 ], cd.zorg + whole );
r.initVert( 7, cd.xorg + half, sq.getY( 4 ), cd.zorg + whole );
r.initVert( 8, cd.xorg + whole, cd.y[ 3 ], cd.zorg + whole );
// Local macro to make the triangle logic shorter & hopefully clearer.
// Make the list of triangles to draw.
if ( ( sq.getEnabledFlags() & 1 ) == 0 )
{
r.tri( 0, 8, 2 );
}
else
{
if ( ( flags & 8 ) != 0 )
{
r.tri( 0, 8, 1 );
}
if ( ( flags & 1 ) != 0 )
{
r.tri( 0, 1, 2 );
}
}
if ( ( sq.getEnabledFlags() & 2 ) == 0 )
{
r.tri( 0, 2, 4 );
}
else
{
if ( ( flags & 1 ) != 0 )
{
r.tri( 0, 2, 3 );
}
if ( ( flags & 2 ) != 0 )
{
r.tri( 0, 3, 4 );
}
}
if ( ( sq.getEnabledFlags() & 4 ) == 0 )
{
r.tri( 0, 4, 6 );
}
else
{
if ( ( flags & 2 ) != 0 )
{
r.tri( 0, 4, 5 );
}
if ( ( flags & 4 ) != 0 )
{
r.tri( 0, 5, 6 );
}
}
if ( ( sq.getEnabledFlags() & 8 ) == 0 )
{
r.tri( 0, 6, 8 );
}
else
{
if ( ( flags & 4 ) != 0 )
{
r.tri( 0, 6, 7 );
}
if ( ( flags & 8 ) != 0 )
{
r.tri( 0, 7, 8 );
}
}
r.done();
return ( num );
}
/**
* Builds the terrain one bank at a time. Each bank is compressed before moving on
* to the next bank.
* @param hm
* @param minDetail
*/
public void buildDatabase( TerrainSampleInterface hm, float minDetail )
{
this.compressBanks();
/*
float lx = hm.getXOrg();
float lz = hm.getZOrg();
float ux = lx + (hm.getXDim() << hm.getScale());
float uz = lz + (hm.getZDim() << hm.getScale());
*/
float bankWidth = ( 2 << bankLevel );
//int bankDim = (2 << bankLevel) / (1 << hm.getScale());
MultiPassSampler mps = new MultiPassSampler( hm );
for ( int i = 0; i < maxBanks; i++ )
{
for ( int j = 0; j < maxBanks; j++ )
{
if ( mps.setBounds( i * bankWidth, j * bankWidth, bankWidth ) )
{
X3DLog.printlnEx( "processing bank ", i, "-", j, " = ", ( i * maxBanks + j ) );
X3DLog.debug( " range is ", (int)mps.getXOrg(), "x", (int)mps.getZOrg(), " - ", ( mps.getXOrg() + ( mps.getXDim() << mps.getScale() ) ) + "x" + ( mps.getZOrg() + ( mps.getZDim() << mps.getScale() ) ) );
AddHeightMap( root, rootData, mps, 0 );
/*
Log.print( " culling" );
this.StaticCullData( root, rootData, minDetail, bankLevel - 1 );
Log.print( " compressing" );
this.compressBanks();
*/
//banks[ i * maxBanks + j ] = TerrainDataBank.compress( banks[ i * maxBanks + j ] );
System.gc();
System.gc();
X3DLog.printlnEx( " done" );
}
}
}
// now compress any banks whihc are basically empty
X3DLog.printlnEx( "final compression" );
this.cullStaticData( minDetail, bankLevel - 1 );
compressBanks();
X3DLog.printlnEx( "done" );
}
/**
* @param filename
* @param hm
* @param lm
*/
public void buildDatabase( String filename, TerrainSampleInterface hm, TerrainSampleInterface lm, float minHighDetail, float minLowDetail ) throws IOException
{
File f = new File( filename );
if ( f.exists() )
f.delete();
Archive a = new Archive( filename, false );
buildDatabase( hm, minHighDetail );
// now save the heightmap
X3DLog.printlnEx( "Saving high res data" );
try
{
for ( int i = 0; i <= maxBanks * maxBanks; i++ )
{
a.write( "BANK_HIGH_" + i, banks[ i ], true );
}
}
catch ( UnscribableNodeEncountered unscribableNodeEncountered )
{
X3DLog.print( unscribableNodeEncountered );
throw new Error( "cannot save data" );
}
X3DLog.printlnEx( "Done Saving data" );
X3DLog.printlnEx( "Nodes for high detail = ", CountNodes() );
for ( int i = 0; i < lm.getScale() + 1; i++ )
deleteStaticData( root, rootData, i );
this.cullStaticData( minLowDetail, bankLevel - 1 );
buildDatabase( lm, minLowDetail );
X3DLog.printlnEx( "Nodes for low detail = " + CountNodes() );
X3DLog.printlnEx( "Saving low res data" );
try
{
for ( int i = 0; i <= maxBanks * maxBanks; i++ )
{
a.write( "BANK_LOW_" + i, banks[ i ], true );
}
}
catch ( UnscribableNodeEncountered unscribableNodeEncountered )
{
X3DLog.print( unscribableNodeEncountered );
throw new Error( "cannot save data" );
}
a.close();
}
class MultiPassSampler implements TerrainSampleInterface
{
float xStart;
float zStart;
int xDim;
int zDim;
TerrainSampleInterface sampler;
MultiPassSampler( TerrainSampleInterface s )
{
this.sampler = s;
}
public boolean setBounds( float x, float z, float width )
{
float lx = x;
float lz = z;
float ux = x + width;
float uz = z + width;
int size = sampler.getXDim() << sampler.getScale();
if ( sampler.getXOrg() > x )
lx = sampler.getXOrg();
else
lx = x;
if ( sampler.getZOrg() > z )
lz = sampler.getZOrg();
else
lz = z;
if ( sampler.getXOrg() + size < ux )
ux = sampler.getXOrg() + size;
if ( sampler.getZOrg() + size < uz )
uz = sampler.getZOrg() + size;
if ( ux - lx <= 0 )
return ( false );
if ( uz - lz <= 0 )
return ( false );
xStart = lx;
zStart = lz;
xDim = (int)( ( ux - lx ) / ( 1 << sampler.getScale() ) );
zDim = (int)( ( uz - lz ) / ( 1 << sampler.getScale() ) );
return ( true );
}
public int getScale()
{
return ( sampler.getScale() );
}
public float sample( int x, int z )
{
return ( sampler.sample( x, z ) );
}
public float getXOrg()
{
return ( xStart );
}
public float getZOrg()
{
return ( zStart );
}
public int getXDim()
{
return ( xDim );
}
public int getZDim()
{
return ( zDim );
}
}
}