/**
* 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.scenegraph;
import org.jagatoo.util.arrays.ArrayUtils;
import org.openmali.vecmath2.Point3f;
import org.xith3d.scenegraph.utils.CopyListener;
/**
* The LODShape3D is a Shape3D Node extension, that handles discrete LOD.
* Discrete LOD (level of detail) is a technique, that
* selects an item depending on its distance to the camera.
*
* @see LODSwitch
*
* @author Marvin Froehlich (aka Qudus)
*/
public abstract class AbstractLODShape3D extends Shape3D
{
private String[] lodNames = new String[ 16 ];
private float[] minDistances = new float[ 16 ];
private float[] maxDistances = new float[ 16 ];
private int numLODs;
private int currentLOD;
private boolean isInitialized = false;
/**
* @param index the index of the LOD item of interest
*
* @return the minimal distance of the LOD item
*/
public final float getMinDist( int index )
{
if ( index >= numLODs )
throw new ArrayIndexOutOfBoundsException( "There's no item with the index " + index );
return ( minDistances[ index ] );
}
/**
* @param index the index of the LOD item of interest
*
* @return the maximal distance of the LOD item
*/
public final float getMaxDist( int index )
{
if ( index >= numLODs )
throw new ArrayIndexOutOfBoundsException( "There's no item with the index " + index );
return ( maxDistances[ index ] );
}
/**
* Returns the name of the LOD with the given index.
*
* @param lod the LOD, for which the name is requested
*
* @return the name of the given LOD
*/
public final String getLODName( int lod )
{
return ( lodNames[ currentLOD ] );
}
/**
* @return the number of LODs
*/
public final int getNumLODs()
{
return ( numLODs );
}
private final int getCurrentLOD()
{
return ( currentLOD );
}
/**
* This eveent is fired when the LOD has changed.<br>
* This is always done by the redner thread.
*
* @param oldLOD the old LOD
* @param newLOD the new LOD
* @param name the name of the new LOD
*/
protected abstract void onLODChanged( int oldLOD, int newLOD, String name );
/**
* Called by the Renderer to make the LODSwitch select the right item for
* the given view position.
*/
public void updateLOD( Point3f viewPosition )
{
//super.setWhichChild( Switch.CHILD_ALL );
//if (true) return;
if ( getNumLODs() == 0 )
{
currentLOD = -1;
return;
}
if ( getCurrentLOD() < 0 )
{
this.currentLOD = 0;
}
final int oldLOD = getCurrentLOD();
Point3f translation = Point3f.fromPool();
this.getWorldTransform().getTranslation( translation );
final float dist = translation.distance( viewPosition );
Point3f.toPool( translation );
if ( isInitialized )
{
if ( dist < minDistances[ oldLOD ] )
{
for ( int i = oldLOD; i >= 0; i-- )
{
if ( ( dist <= maxDistances[ i ] ) && ( dist >= minDistances[ i ] ) )
{
currentLOD = i;
onLODChanged( oldLOD, currentLOD, lodNames[ currentLOD ] );
return;
}
}
currentLOD = -1;
}
else if ( dist > maxDistances[ oldLOD ] )
{
for ( int i = oldLOD; i < getNumLODs(); i++ )
{
if ( ( dist <= maxDistances[ i ] ) && ( dist >= minDistances[ i ] ) )
{
currentLOD = i;
onLODChanged( oldLOD, currentLOD, lodNames[ currentLOD ] );
return;
}
}
currentLOD = -1;
}
}
else
{
currentLOD = -1;
for ( int i = 0; i < getNumLODs(); i++ )
{
if ( ( dist <= maxDistances[ i ] ) && ( dist >= minDistances[ i ] ) )
{
currentLOD = i;
break;
}
}
if ( currentLOD != -1 )
onLODChanged( -1, currentLOD, lodNames[ currentLOD ] );
isInitialized = true;
}
}
/**
* Adds a new LOD (level of detail).
*
* @param name the name of the new LOD
* @param minDist the minimum distance
* @param maxDist the maximum distance
*/
protected int addLOD( String name, float minDist, float maxDist )
{
if ( minDist > maxDist )
throw new IllegalArgumentException( "minDist (" + minDist + ") is greater than maxDist (" + maxDist + ")" );
int level = -1;
if ( getNumLODs() == 0 )
{
level = 0;
minDistances[ level ] = minDist;
maxDistances[ level ] = maxDist;
currentLOD = level;
lodNames = ArrayUtils.ensureCapacity( lodNames, String.class, 1 );
lodNames[ level ] = name;
}
else
{
for ( int i = 0; i < getNumLODs(); i++ )
{
if ( minDistances[ i ] > minDist )
{
level = i;
}
else if ( maxDistances[ i ] < maxDist )
{
level = i + 1;
}
}
if ( level < 0 )
throw new IllegalArgumentException( "Don't know where to insert this item." );
if ( ( level > 0 ) && ( minDistances[ level - 1 ] > minDist ) )
throw new IllegalArgumentException( "minDist MUST NOT overlap two items." );
if ( ( level < getNumLODs() - 1 ) && ( maxDistances[ level + 1 ] < maxDist ) )
throw new IllegalArgumentException( "maxDist MUST NOT overlap two items." );
minDistances = ArrayUtils.ensureCapacity( minDistances, getNumLODs() + 1 );
maxDistances = ArrayUtils.ensureCapacity( maxDistances, getNumLODs() + 1 );
lodNames = ArrayUtils.ensureCapacity( lodNames, String.class, getNumLODs() + 1 );
if ( level < getNumLODs() )
{
System.arraycopy( minDistances, level, minDistances, level + 1, getNumLODs() - level );
System.arraycopy( maxDistances, level, maxDistances, level + 1, getNumLODs() - level );
System.arraycopy( lodNames, level, lodNames, level + 1, getNumLODs() - level );
}
minDistances[ level ] = minDist;
maxDistances[ level ] = maxDist;
lodNames[ level ] = name;
}
numLODs++;
return ( level );
}
/**
* Adds a new (unnamed) LOD (level of detail).
*
* @param minDist the minimum distance
* @param maxDist the maximum distance
*/
protected final int addLOD( float minDist, float maxDist )
{
return ( addLOD( (String)null, minDist, maxDist ) );
}
/**
* {@inheritDoc}
*/
@Override
protected abstract AbstractLODShape3D newInstance();
/**
* {@inheritDoc}
*/
@Override
protected void copy( Shape3D dst )
{
super.copy( dst );
AbstractLODShape3D newShape = (AbstractLODShape3D)dst;
if ( newShape.lodNames.length != this.lodNames.length )
newShape.lodNames = new String[ this.lodNames.length ];
System.arraycopy( this.lodNames, 0, newShape.lodNames, 0, this.lodNames.length );
if ( newShape.minDistances.length != this.minDistances.length )
newShape.minDistances = new float[ this.minDistances.length ];
System.arraycopy( this.minDistances, 0, newShape.minDistances, 0, this.minDistances.length );
if ( newShape.maxDistances.length != this.maxDistances.length )
newShape.maxDistances = new float[ this.maxDistances.length ];
System.arraycopy( this.maxDistances, 0, newShape.maxDistances, 0, this.maxDistances.length );
newShape.numLODs = this.numLODs;
newShape.currentLOD = this.currentLOD;
newShape.isInitialized = this.isInitialized;
}
/**
* {@inheritDoc}
*/
@Override
public AbstractLODShape3D sharedCopy( CopyListener listener )
{
return ( (AbstractLODShape3D)super.sharedCopy( listener ) );
}
/**
* {@inheritDoc}
*/
@Override
public AbstractLODShape3D sharedCopy()
{
return ( (AbstractLODShape3D)super.sharedCopy() );
}
/**
* Constructs a new Shape3D object with specified geometry and
* appearance components.
*/
public AbstractLODShape3D( Geometry geometry, Appearance appearance )
{
super( geometry, appearance );
this.numLODs = 0;
this.currentLOD = -1;
}
/**
* Constructs a new LODShape3D object with specified geometry component
* and a null appearance component.
*/
public AbstractLODShape3D( Geometry geometry )
{
this( geometry, (Appearance)null );
}
/**
* Constructs a new LODShape3D object with a null geometry component
* and a null appearance component.
*/
public AbstractLODShape3D()
{
this( (Geometry)null, (Appearance)null );
}
}