/**
* 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.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.openmali.FastMath;
import org.w3c.dom.Document;
import org.xith3d.utility.logging.X3DLog;
/**
* @author Mathias 'cylab' Henze
*/
@SuppressWarnings("unchecked")
public class L3DTResourceProvider extends DefaultGridResourceProvider
{
private static XPath xpath = XPathFactory.newInstance().newXPath();
private float minAlt;
private float maxAlt;
private float hScale;
private long nx;
private long ny;
public final float getHScale()
{
return ( hScale );
}
public final float getMaxAlt()
{
return ( maxAlt );
}
public final float getMinAlt()
{
return ( minAlt );
}
public final long getNx()
{
return ( nx );
}
public final long getNy()
{
return ( ny );
}
private static int createMosaicSpecs( Properties mosaic, List<GridResourceSpec> specs, long bestWidth, long bestHeight, float min, float max ) throws MalformedURLException
{
URL location = new URL( mosaic.getProperty( "Location" ) );
String baseName = mosaic.getProperty( "BaseName" );
long width = Long.parseLong( mosaic.getProperty( "nPxlsX" ) );
long height = Long.parseLong( mosaic.getProperty( "nPxlsY" ) );
long tilesX = Long.parseLong( mosaic.getProperty( "nMapsX" ) );
long tilesY = Long.parseLong( mosaic.getProperty( "nMapsY" ) );
String ext = mosaic.getProperty( "FileExt" );
float dx = 1.0f / tilesX;
float dy = 1.0f / tilesY;
int detail = (int)( (float)bestWidth / width + 0.5 ) + (int)( (float)bestHeight / height + 0.5 ) / 2;
// log2
for ( int i = 0; i < 32; i++ )
{
if( detail >> i == 0 )
{
// this is a quick hack. The value is dependent on the ChunkedTerrain Setup (spatialTreeDepth)
detail = i / 2;
break;
}
}
for ( int x = 0; x < tilesX; x++ )
{
for ( int y = 0; y < tilesY; y++ )
{
specs.add( new GridResourceSpec(
detail, new URL[] { new URL( location, baseName + "_x" + x + "y" + y + "." + ext ) },
dx * x, dy * y, dx * ( x + 1 ), dy * ( y + 1 ),
min, max
) );
}
}
return ( detail );
}
private static List<Properties> findMipMaps( URL project, String type )
{
ArrayList<Properties> result = new ArrayList<Properties>( 8 );
try
{
for ( int i = 1; i <= 8; i++ )
{
String mip = "Mip" + ( FastMath.pow( 2, i ) );
URL manifestLocation = new URL( project, "MipMaps/" + type + "/" + mip + "/" + type + "_" + mip + ".mmf" );
if ( checkUrl( manifestLocation ) )
{
Properties manifest = loadManifest( manifestLocation );
if ( manifest == null )
{
break;
}
result.add( manifest );
}
else
{
break;
}
}
}
catch ( Exception ex )
{
Logger.getLogger( L3DTResourceProvider.class.getName() ).log( Level.SEVERE, null, ex );
}
return ( result );
}
private static boolean checkUrl( URL location )
{
try
{
return ( location.openConnection().getContentLength() > 0 );
}
catch ( Exception ignore )
{
return ( false );
}
}
private static Document loadXML( URL location )
{
InputStream in = null;
try
{
in = location.openStream();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware( true );
factory.setValidating( false );
Document doc = factory.newDocumentBuilder().parse( in );
return ( doc );
}
catch ( Exception ex )
{
Logger.getLogger( L3DTResourceProvider.class.getName() ).log( Level.SEVERE, null, ex );
}
finally
{
if ( in != null )
{
try { in.close(); } catch ( Exception ignore ) {}
}
}
return ( null );
}
private static Properties loadManifest( URL location ) throws Exception
{
/*
* This is for L3DT itself, which puts backslashes into the xml file.
* Since there's no way to keep it from doing so we will (have to)
* replace a backslash with a slash.
*/
location = new URL( location.toString().replace( '\\', '/' ) );
BufferedReader in = null;
try
{
Properties props = new Properties();
in = new BufferedReader( new InputStreamReader( location.openStream() ) );
String line = "";
while ( ( line = in.readLine() ) != null )
{
if ( line.startsWith( "#" ) && !line.startsWith( "#EOF" ) )
{
String[] values = line.split( "[#:\t ]" );
if (values.length == 4 )
props.setProperty( values[ 1 ], values[ 3 ] );
else if ( values.length == 5 )
props.setProperty( values[ 1 ] + "." + values[ 3 ], values[ 4 ] );
}
}
String locationString = location.toString();
String baseName = locationString.substring( locationString.lastIndexOf( "/" ) + 1, locationString.lastIndexOf( "." ) );
props.setProperty( "Location", locationString );
props.setProperty( "BaseName", baseName );
return ( props );
}
catch ( Exception ex )
{
Logger.getLogger( L3DTResourceProvider.class.getName() ).log( Level.SEVERE, null, ex );
}
finally
{
if ( in != null )
{
try { in.close(); } catch ( Exception ignore ) {}
}
}
return ( null );
}
private static String buildXPath( String spec, String type )
{
String xpathexp = spec.replaceAll( "([^\\/]+)\\/", "varlist[@name='$1']/" );
xpathexp = xpathexp.replaceAll( "([^\\/]+)$", type + "[@name='$1']" );
return ( xpathexp );
}
private static float getFloat( Document doc, String spec ) throws XPathExpressionException
{
String value = xpath.evaluate( buildXPath( spec, "float" ), doc );
return ( Float.parseFloat( value ) );
}
private static long getInt( Document doc, String spec ) throws XPathExpressionException
{
String value = xpath.evaluate( buildXPath( spec, "int" ), doc );
return ( Long.parseLong( value ) );
}
private static String getString( Document doc, String spec ) throws XPathExpressionException
{
return ( xpath.evaluate( buildXPath( spec, "string" ), doc ) );
}
public L3DTResourceProvider( URL l3dtProject )
{
ArrayList<GridResourceSpec> samplerSpecs = new ArrayList<GridResourceSpec>( 100 );
ArrayList<GridResourceSpec> surfaceSpecs = new ArrayList<GridResourceSpec>( 100 );
Document doc = loadXML( l3dtProject );
try
{
nx = getInt( doc, "/ProjectData/MapInfo/Terrain/nx" );
ny = getInt( doc, "/ProjectData/MapInfo/Terrain/ny" );
minAlt = getFloat( doc, "/ProjectData/MapInfo/Terrain/MinAlt" );
maxAlt = getFloat( doc, "/ProjectData/MapInfo/Terrain/MaxAlt" );
hScale = getFloat( doc, "/ProjectData/MapInfo/Terrain/HorizScale" );
String heightFieldLocation = getString( doc, "/ProjectData/Maps/HF/Filename" );
List<Properties> mipMaps = findMipMaps( l3dtProject, "HF" );
int maxDetail = 0;
for ( int i = 0; i < mipMaps.size(); i++ )
{
int value = createMosaicSpecs( mipMaps.get( i ), samplerSpecs, nx, ny, minAlt, maxAlt );
if ( value > maxDetail )
maxDetail = value;
}
if ( heightFieldLocation.toString().endsWith( ".mmf" ) )
{
createMosaicSpecs( loadManifest( new URL( l3dtProject, heightFieldLocation ) ), samplerSpecs, nx, ny, minAlt, maxAlt );
}
else
{
samplerSpecs.add( new GridResourceSpec( 0, new URL( l3dtProject, heightFieldLocation ), 0.0f, 0.0f, 1.0f, 1.0f, minAlt, maxAlt ) );
}
// reverse the detail, since 0 is our lowest level detail
for ( int i = 0; i < samplerSpecs.size(); i++ )
{
GridResourceSpec<GridSampler> spec = samplerSpecs.get( i );
spec.setDetail( maxDetail - spec.getDetail() );
}
long textureWidth = getInt( doc, "/ProjectData/MapInfo/Texture/nx" );
long textureHeight = getInt( doc, "/ProjectData/MapInfo/Texture/ny" );
String textureLocation = getString( doc, "/ProjectData/Maps/TX/Filename" );
mipMaps = findMipMaps( l3dtProject, "TX" );
maxDetail = 0;
for ( int i = 0; i < mipMaps.size(); i++ )
{
int value = createMosaicSpecs( mipMaps.get( i ), surfaceSpecs, textureWidth, textureHeight, 0f, 0f );
if ( value > maxDetail )
maxDetail = value;
}
if ( textureLocation.toString().endsWith( ".mmf" ) )
{
createMosaicSpecs( loadManifest( new URL( l3dtProject, textureLocation ) ), surfaceSpecs, textureWidth, textureHeight, 0f, 0f );
}
else
{
surfaceSpecs.add( new GridResourceSpec( 0, new URL( l3dtProject, textureLocation ), 0.0f, 0.0f, 1.0f, 1.0f ) );
}
// reverse the detail, since 0 is our lowest level detail
for ( int i = 0; i < surfaceSpecs.size(); i++ )
{
GridResourceSpec<GridSurface> spec = surfaceSpecs.get( i );
spec.setDetail( maxDetail - spec.getDetail() );
}
}
catch ( Exception ex )
{
X3DLog.print( ex );
Logger.getLogger( L3DTResourceProvider.class.getName() ).log( Level.SEVERE, null, ex );
}
setSamplerSpecs( samplerSpecs );
setSurfaceSpecs( surfaceSpecs );
}
}