/**
* 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;
import java.util.ArrayList;
import org.openmali.FastMath;
import org.openmali.vecmath2.Point3f;
import org.openmali.vecmath2.Tuple3f;
import org.xith3d.scenegraph.Group;
import org.xith3d.scenegraph.Transform3D;
import org.xith3d.scenegraph.TransformGroup;
import org.xith3d.scenegraph.LODSwitch;
import org.xith3d.scenegraph.StaticTransform;
import org.xith3d.scenegraph.LazyLoadable;
/**
* @author Mathias 'cylab' Henze
*/
public class ChunkedTerrain extends Group implements LazyLoadable
{
protected static class Spec
{
public GridResourceProvider resourceProvider;
public float x;
public float y;
public float z;
public float scale;
public float height;
public Spec()
{
}
public Spec( GridResourceProvider resourceProvider, float x, float y, float z, float scale, float height )
{
this.resourceProvider = resourceProvider;
this.x = x;
this.y = y;
this.z = z;
this.scale = scale;
this.height = height;
}
}
private ArrayList<GridSampler> samplers;
private ArrayList<GridSurface> surfaces;
private GridResourceProvider resourceProvider;
private Point3f location;
private Tuple3f dimension;
private float scale = 1.0f;
private float height = 1.0f;
private float x;
private float y;
private float z;
private float parentYOffset;
private int tilesPerSide;
private float s1 = 0f;
private float t1 = 0f;
private float s2 = 1.0f;
private float t2 = 1.0f;
// ---
private int spatialTreeDepth = 2;
private int geomTreeDepth;
private int maxGeomTreeDepth;
private int complexity = 4;
private float baseTolerance = 0.1f;
private boolean isSetUp= false;
private boolean isLazy= false;
private Group content= null;
public ChunkedTerrain( GridSampler gridSampler, GridSurface gridShader, float x,float y, float z, float scale, float height )
{
this( new SimpleGridResourceProvider(gridSampler, gridShader), x, y, z,scale,height );
}
public ChunkedTerrain( GridResourceProvider resourceProvider, float x, float y, float z, float scale, float height )
{
this( resourceProvider, x, y, z,scale,height, 0f, 0f, 1.0f, 1.0f, 0f, 0, 2, false );
}
protected ChunkedTerrain( Spec spec)
{
this(spec.resourceProvider, spec.x, spec.y, spec.z, spec.scale, spec.height);
}
protected ChunkedTerrain( GridResourceProvider resourceProvider, float x,float y, float z, float scale, float height, float s1, float t1, float s2, float t2, float parentYOffset, int geomTreeDepth, int maxGeomTreeDepth, boolean lazy)
{
this.resourceProvider = resourceProvider;
this.x = x;
this.y = y;
this.z = z;
this.scale = scale;
this.height = height;
this.geomTreeDepth = geomTreeDepth;
this.maxGeomTreeDepth = maxGeomTreeDepth;
this.s1 = s1;
this.t1 = t1;
this.s2 = s2;
this.t2 = t2;
this.parentYOffset = parentYOffset;
this.isLazy = lazy;
this.dimension = new Tuple3f( scale, height, scale );
this.location = new Point3f( x, y, z );
this.tilesPerSide = FastMath.pow( 2, spatialTreeDepth );
this.samplers = new ArrayList<GridSampler>( tilesPerSide * tilesPerSide );
this.surfaces = new ArrayList<GridSurface>( tilesPerSide * tilesPerSide );
if ( !lazy )
{
prepare();
setUp();
}
else
{
content = new Group();
}
}
public float getHeight()
{
return height;
}
public float getX()
{
return x;
}
public float getY()
{
return y;
}
public float getZ()
{
return z;
}
public Point3f getLocation()
{
return location;
}
public Tuple3f getDimension()
{
return dimension;
}
public float pickY(float x, float z)
{
float s = Math.max( 0, Math.min( 1, ((x - this.x) / scale) - s1 ) );
float t = Math.max( 0, Math.min( 1, ((z - this.z) / scale) - t1 ) );
GridSampler gridSampler= resourceProvider.findSampler( s,t,s,t,-1 );
return gridSampler.sampleHeight(s, t)*height+y;
}
private void addTreeLevel( Group parent, int x1, int z1, int x2, int z2, int l )
{
int xc = ( x1 + x2 ) / 2;
int zc = ( z1 + z2 ) / 2;
if ( l == spatialTreeDepth - 1 )
{
addTiles( parent, x1, z1, xc, zc );
addTiles( parent, xc, z1, x2, zc );
addTiles( parent, xc, zc, x2, z2 );
addTiles( parent, x1, zc, xc, z2 );
return;
}
l++;
Group nwGroup = new Group();
addTreeLevel( nwGroup, x1, z1, xc, zc, l );
parent.addChild( nwGroup );
Group neGroup = new Group();
addTreeLevel( neGroup, xc, z1, x2, zc, l );
parent.addChild( neGroup );
Group seGroup = new Group();
addTreeLevel( seGroup, xc, zc, x2, z2, l );
parent.addChild( seGroup );
Group swGroup = new Group();
addTreeLevel( swGroup, x1, zc, xc, z2, l );
parent.addChild( swGroup );
}
private void addTiles( Group group, int x1, int z1, int x2, int z2 )
{
float ds = s2 - s1;
float dt = t2 - t1;
for ( int ix = x1; ix < x2; ix++ )
{
for ( int iz = z1; iz < z2; iz++ )
{
GridSampler gridSampler= resourceProvider.findSampler( s1 + ( ds / tilesPerSide ) * ix, t1 + ( dt / tilesPerSide ) * iz, s1 + ( ds / tilesPerSide ) * ( ix + 1 ), t1 + ( dt / tilesPerSide ) * ( iz + 1 ), geomTreeDepth );
GridSurface gridSurface= resourceProvider.findSurface( s1 + ( ds / tilesPerSide ) * ix, t1 + ( dt / tilesPerSide ) * iz, s1 + ( ds / tilesPerSide ) * ( ix + 1 ), t1 + ( dt / tilesPerSide ) * ( iz + 1 ), geomTreeDepth );
samplers.add(gridSampler);
surfaces.add(gridSurface);
float xoffset = ( scale / tilesPerSide ) / 2;
GridTriangulator triangulator = new GridTriangulator( gridSampler, complexity, height, 0 - xoffset, 0 - xoffset, ( scale / tilesPerSide ) - xoffset, ( scale / tilesPerSide ) - xoffset, s1 + ( ds / tilesPerSide ) * ix, t1 + ( dt / tilesPerSide ) * iz, s1 + ( ds / tilesPerSide ) * ( ix + 1 ), t1 + ( dt / tilesPerSide ) * ( iz + 1 ), baseTolerance );
float yoffset = triangulator.getMinY()+(triangulator.getMaxY()-triangulator.getMinY())/2;
TerrainTile terrainTile = new TerrainTile( gridSampler, gridSurface ,triangulator , geomTreeDepth );
StaticTransform.translate (terrainTile,0,-yoffset,0);
TransformGroup transform = new TransformGroup( new Transform3D( this.x + ( scale / tilesPerSide ) * ix + xoffset, this.y+yoffset-parentYOffset, this.z + ( scale / tilesPerSide ) * iz + xoffset ) );
if ( geomTreeDepth < maxGeomTreeDepth )
{
final float threshold = scale / tilesPerSide * 1.5f;
LODSwitch lodSwitch = new LODSwitch();
ChunkedTerrain subTerrain = new ChunkedTerrain( resourceProvider, 0 - xoffset, 0, 0 - xoffset, scale / tilesPerSide, height, s1 + ( ds / tilesPerSide ) * ix, t1 + ( dt / tilesPerSide ) * iz, s1 + ( ds / tilesPerSide ) * ( ix + 1 ), t1 + ( dt / tilesPerSide ) * ( iz + 1 ), yoffset, geomTreeDepth+1,maxGeomTreeDepth, true );
lodSwitch.addLODItem( terrainTile, threshold, Float.MAX_VALUE );
lodSwitch.addLODItem( subTerrain, 0, threshold );
transform.addChild( lodSwitch );
}
else
{
transform.addChild( terrainTile );
}
group.addChild( transform );
}
}
}
public void prepare()
{
// System.out.println("prepare called for "+getName()+"!");
if(!isSetUp)
{
addTreeLevel( isLazy?content:this, 0, 0, tilesPerSide, tilesPerSide, 0 );
}
// System.out.println("prepare of "+name+" finished!");
}
public void setUp()
{
// System.out.println("setUp called for "+name+"!");
if(!isSetUp)
{
if(isLazy)this.addChild(content);
isSetUp=true;
}
// System.out.println("setUp of "+name+" finished!");
}
public void tearDown()
{
if(isLazy && isSetUp)
{
this.removeChild( content );
content = new Group();
isSetUp=false;
}
}
public boolean isSetUp()
{
return isSetUp;
}
public void cleanUp()
{
if(isLazy && isSetUp)
{
int size = samplers.size();
for( int i = 0; i < size; i++ )
{
resourceProvider.releaseSampler(samplers.remove(0));
}
size = surfaces.size();
for( int i = 0; i < size; i++ )
{
resourceProvider.releaseSurface(surfaces.remove(0));
}
}
}
}