/** * 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.IOException; import org.xith3d.io.InvalidFormat; import org.xith3d.io.Scribable; import org.xith3d.io.ScribeInputStream; import org.xith3d.io.ScribeOutputStream; import org.xith3d.io.UnscribableNodeEncountered; import org.xith3d.utility.logging.X3DLog; /** * * Terrain data bank holds terrain information for a given portion of the * world. Banks can be read and written with great speed from disk * because they are organized in large arrays.<br> * <br> * Multiple banks can be used for the same portion of the world, stored at different * densities. Then the terrain system can use the lower density bank for areas far away, * saving on memory.<br> * <br> * * @author David Yazel */ public class TerrainDataBank implements Scribable { public static final int MUL_VERTEX = 5; public static final int MUL_ERROR = 6; public static final int MUL_SUB_ENABLED = 2; public static final int MUL_CHILD = 4; public static final int MUL_STATIC = 1; public static final int MUL_DIRTY = 1; public static final int MUL_NEXT = 1; public static final int MUL_ENABLED = 1; public static final int MUL_MINMAX = 1; //private final int MAX_NODES = 100000; private final static int EXTENT_NODES = 10000; private int[] child; // this is the node within a bank private int[] childBank; // this is the bank that the child exists in protected int[] handles; // indirection pointers into the vertices. private float[] y; private short[] error; private short[] minY; private short[] maxY; private byte[] enabledFlags; private byte[] subEnabledCount; private byte[] dataStatic; private byte[] dataDirty; private int[] next; private boolean compressed = false; private int bankId; protected TerrainDataBank newBank = null; // total number of nodes allocated private int maxNodes = 0; protected int freeList = 0; private int freeHandle = 0; public boolean isCompressed() { return ( compressed ); } private static void copy( TerrainDataBank db, TerrainDataBank d ) { System.arraycopy( db.child, 0, d.child, 0, db.child.length ); System.arraycopy( db.childBank, 0, d.childBank, 0, db.childBank.length ); System.arraycopy( db.y, 0, d.y, 0, db.y.length ); System.arraycopy( db.minY, 0, d.minY, 0, db.minY.length ); System.arraycopy( db.maxY, 0, d.maxY, 0, db.maxY.length ); System.arraycopy( db.error, 0, d.error, 0, db.error.length ); System.arraycopy( db.enabledFlags, 0, d.enabledFlags, 0, db.enabledFlags.length ); System.arraycopy( db.subEnabledCount, 0, d.subEnabledCount, 0, db.subEnabledCount.length ); System.arraycopy( db.dataStatic, 0, d.dataStatic, 0, db.dataStatic.length ); System.arraycopy( db.dataDirty, 0, d.dataDirty, 0, db.dataDirty.length ); System.arraycopy( db.next, 0, d.next, 0, db.next.length ); } private void deleteData() { child = null; childBank = null; y = null; minY = null; maxY = null; error = null; enabledFlags = null; subEnabledCount = null; dataStatic = null; dataDirty = null; handles = null; next = null; } public static TerrainDataBank expand( TerrainDataBank db ) { TerrainDataBank d = new TerrainDataBank( db.bankId, db.maxNodes + EXTENT_NODES ); d.freeList = db.maxNodes; if ( db.handles.length >= d.maxNodes ) { X3DLog.debug( "keeping bigger handle size" ); d.handles = new int[ db.handles.length ]; System.arraycopy( db.handles, 0, d.handles, 0, db.handles.length ); d.freeHandle = db.freeHandle; } else { X3DLog.debug( "extanding handle size for bank ", db.bankId, " from ", db.handles.length, " to ", d.maxNodes ); System.arraycopy( db.handles, 0, d.handles, 0, db.handles.length ); // ok we need to fix up the free handle list. all the new handles need // to be added to the free list d.handles[ d.handles.length - 1 ] = -db.freeHandle; d.freeHandle = db.handles.length; } copy( db, d ); db.newBank = d; db.deleteData(); return ( d ); } public void delete( int node ) { if ( ( bankId == 11 ) && ( node == 13 ) ) { X3DLog.debug( "node 11/13 deleted" ); } for ( int i = 0; i < MUL_CHILD; i++ ) { setChild( node, i, -1 ); setChildBank( node, i, -1 ); } next[ handles[ node ] ] = freeList; freeList = handles[ node ]; // fix the list of free handles handles[ node ] = -freeHandle; freeHandle = node; } /** * copies the specified node from the source into the dest. This is used * to compress the databank into a new bank */ private static int copyNode( int node, TerrainDataBank source, TerrainDataBank dest ) { int n = dest.freeList; if ( n == -1 ) throw new Error( "Out of quad nodes to allocate" ); dest.freeList = dest.next[ n ]; dest.next[ n ] = -1; dest.handles[ node ] = n; //Log.print("Copying node "+node+" to location "+n); try { System.arraycopy( source.y, source.handles[ node ] * MUL_VERTEX, dest.y, dest.handles[ node ] * MUL_VERTEX, MUL_VERTEX ); System.arraycopy( source.minY, source.handles[ node ] * MUL_MINMAX, dest.minY, dest.handles[ node ] * MUL_MINMAX, MUL_MINMAX ); System.arraycopy( source.maxY, source.handles[ node ] * MUL_MINMAX, dest.maxY, dest.handles[ node ] * MUL_MINMAX, MUL_MINMAX ); System.arraycopy( source.error, source.handles[ node ] * MUL_ERROR, dest.error, dest.handles[ node ] * MUL_ERROR, MUL_ERROR ); System.arraycopy( source.enabledFlags, source.handles[ node ] * MUL_ENABLED, dest.enabledFlags, dest.handles[ node ] * MUL_ENABLED, MUL_ENABLED ); System.arraycopy( source.subEnabledCount, source.handles[ node ] * MUL_SUB_ENABLED, dest.subEnabledCount, dest.handles[ node ] * MUL_SUB_ENABLED, MUL_SUB_ENABLED ); System.arraycopy( source.dataStatic, source.handles[ node ] * MUL_STATIC, dest.dataStatic, dest.handles[ node ] * MUL_STATIC, MUL_STATIC ); System.arraycopy( source.dataDirty, source.handles[ node ] * MUL_DIRTY, dest.dataDirty, dest.handles[ node ] * MUL_DIRTY, MUL_DIRTY ); System.arraycopy( source.child, source.handles[ node ] * MUL_CHILD, dest.child, dest.handles[ node ] * MUL_CHILD, MUL_CHILD ); System.arraycopy( source.childBank, source.handles[ node ] * MUL_CHILD, dest.childBank, dest.handles[ node ] * MUL_CHILD, MUL_CHILD ); } catch ( Throwable e ) { X3DLog.exception( "Error attempting to compress" ); X3DLog.exception( " node = ", node ); X3DLog.exception( " source location = ", source.handles[ node ] ); X3DLog.exception( " dest location = ", dest.handles[ node ] ); throw new Error( e ); } return ( n ); } /** * Compresses the data bank by moving the non-deleted nodes down near the front and then * building a new version which is shorter. This should be done after a static cull to * save disk space and memory. <b>This wont be super fast.</b> * * @param db * * @return the new TerrainDataBank */ public static TerrainDataBank compress( TerrainDataBank db ) { //if (db.compressed) return ( db ); /* if (db.next[0]!=-1) { TerrainDataBank d = new TerrainDataBank( 10 ); db.newBank = d; return ( d ); } */ // count the number of nodes which are *not* deleted int num = 0; for ( int i = 0; i < db.maxNodes; i++ ) if ( db.next[ i ] == -1 ) num++; X3DLog.debug( "compressing bank ", db.bankId, " to ", num, " nodes from ", db.maxNodes ); // find the bank, and make sure that all the banks are the same for ( int i = 0; i < db.maxNodes * MUL_CHILD; i++ ) { if ( db.childBank[ i ] != -1 ) { if ( db.bankId != db.childBank[ i ] ) { X3DLog.error( "we encountered a node in bank ", db.bankId, " that has a child in bank ", db.childBank[ i ] ); X3DLog.error( "index is ", i ); throw new Error( "Child exists outside of bank" ); } } } // ok now we have the number of nodes and we need to make a new bank with jsut that many nodes TerrainDataBank d = new TerrainDataBank( db.bankId, ( num + 10 ) ); d.handles = new int[ db.handles.length ]; System.arraycopy( db.handles, 0, d.handles, 0, db.handles.length ); d.freeHandle = db.freeHandle; db.newBank = d; // copy all the nodes for ( int i = 0; i < db.handles.length; i++ ) { if ( db.handles[ i ] >= 0 ) copyNode( i, db, d ); } d.compressed = true; db.deleteData(); // now do some validation for ( int i = 0; i < d.handles.length; i++ ) { if ( d.handles[ i ] >= 0 ) { // make sure all children are valid pointers for this node for ( int j = 0; j < MUL_CHILD; j++ ) { int child = d.getChild( i, j ); if ( child != -1 ) if ( d.handles[ child ] < 0 ) { throw new Error( "Terrain Data Bank handles are corrupt" ); } } } } // find the bank, and make sure that all the banks are the same for ( int i = 0; i < d.maxNodes * MUL_CHILD; i++ ) { if ( d.childBank[ i ] != -1 ) { if ( d.bankId != d.childBank[ i ] ) { X3DLog.error( "POST COMPRESSION ERROR" ); X3DLog.error( "we encountered a node in bank ", d.bankId, " that has a child in bank ", d.childBank[ i ] ); throw new Error( "Child exists outside of bank" ); } } } return ( d ); } // we store all the quad squares broken down into arrays so we can load // and unload data extremely quickly public TerrainDataBank() { } public TerrainDataBank( int bankId, int max ) { this.bankId = bankId; initialize( max ); } /** * Takes a node off the free list and returns the index * * @return the new handle */ public int allocateNode() { int n = freeList; if ( n == -1 ) throw new Error( "Out of quad nodes to allocate" ); int h = freeHandle; if ( h < 0 ) throw new Error( "out of handles to allocate" ); freeList = next[ n ]; next[ n ] = -1; if ( handles[ freeHandle ] >= 0 ) { X3DLog.error( "we are allocating a handle already allocated!" ); System.exit( 0 ); } freeHandle = -handles[ h ]; handles[ h ] = n; for ( int i = 0; i < MUL_CHILD; i++ ) child[ n * MUL_CHILD + i ] = -1; if ( ( bankId == 11 ) && ( h == 13 ) ) X3DLog.debug( "node 11/13 allocated" ); //Log.print( "allocated node " + n + " with handle " + h + " in bank " + bankId ); return ( h ); } public float getY( int node, int index ) { return ( y[ handles[ node ] * MUL_VERTEX + index ] ); } public void setY( int node, int index, float val ) { y[ handles[ node ] * MUL_VERTEX + index ] = val; } public short getError( int node, int index ) { return ( error[ handles[ node ] * MUL_ERROR + index ] ); } public void setError( int node, int index, short val ) { error[ handles[ node ] * MUL_ERROR + index ] = val; } public int getChild( int node, int index ) { if ( ( bankId == 11 ) && ( child[ handles[ node ] * MUL_CHILD + index ] == 13 ) ) X3DLog.debug( "node 11/13 is child ", index, " of node ", node ); return ( child[ handles[ node ] * MUL_CHILD + index ] ); } public int getChildBank( int node, int index ) { return ( childBank[ handles[ node ] * MUL_CHILD + index ] ); } public void setChild( int node, int index, int val ) { child[ handles[ node ] * MUL_CHILD + index ] = val; } public void setChildBank( int node, int index, int val ) { //if (val != bankId) throw new Error( "Attempting to set child bank as " + val + " inside bank " + bankId ) ); childBank[ handles[ node ] * MUL_CHILD + index ] = val; } public byte getSubEnabledCount( int node, int index ) { return ( subEnabledCount[ handles[ node ] * MUL_SUB_ENABLED + index ] ); } public void setSubEnabledCount( int node, int index, byte val ) { subEnabledCount[ handles[ node ] * MUL_SUB_ENABLED + index ] = val; } public short getMinY( int node ) { return ( minY[ handles[ node ] * MUL_MINMAX ] ); } public void setMinY( int node, short val ) { minY[ handles[ node ] * MUL_MINMAX ] = val; } public byte getEnabledFlags( int node ) { return ( enabledFlags[ handles[ node ] * MUL_ENABLED ] ); } public void setEnabledFlags( int node, byte val ) { enabledFlags[ handles[ node ] * MUL_ENABLED ] = val; } public short getMaxY( int node ) { return ( maxY[ handles[ node ] * MUL_MINMAX ] ); } public void setMaxY( int node, short val ) { maxY[ handles[ node ] * MUL_MINMAX ] = val; } public boolean getStatic( int node ) { return ( dataStatic[ handles[ node ] * MUL_STATIC ] == 1 ); } public void setStatic( int node, boolean val ) { dataStatic[ handles[ node ] * MUL_STATIC ] = val ? (byte)1 : (byte)0; } public boolean getDirty( int node ) { return ( dataDirty[ handles[ node ] * MUL_DIRTY ] == 1 ); } public void setDirty( int node, boolean val ) { dataDirty[ handles[ node ] * MUL_DIRTY ] = val ? (byte)1 : (byte)0; } public void setNext( int node, int val ) { next[ handles[ node ] * MUL_NEXT ] = val; } public int getNext( int node ) { return ( next[ handles[ node ] * MUL_NEXT ] ); } public void initialize( int max ) { handles = new int[ max ]; child = new int[ max * MUL_CHILD ]; childBank = new int[ max * MUL_CHILD ]; y = new float[ max * MUL_VERTEX ]; enabledFlags = new byte[ max * MUL_ENABLED ]; error = new short[ max * MUL_ERROR ]; minY = new short[ max * MUL_MINMAX ]; maxY = new short[ max * MUL_MINMAX ]; subEnabledCount = new byte[ max * MUL_SUB_ENABLED ]; dataDirty = new byte[ max * MUL_DIRTY ]; dataStatic = new byte[ max * MUL_STATIC ]; maxNodes = max; // define the free list next = new int[ max * MUL_NEXT ]; for ( int i = 0; i < max; i++ ) { next[ i ] = i + 1; handles[ i ] = -i - 1; } for ( int i = 0; i < max * MUL_CHILD; i++ ) { child[ i ] = -1; childBank[ i ] = -1; } next[ max - 1 ] = -1; } public void load( ScribeInputStream in ) throws InvalidFormat, IOException { bankId = in.readInt(); maxNodes = in.readInt(); freeList = in.readInt(); freeHandle = in.readInt(); handles = in.readIntArray(); next = in.readIntArray(); child = in.readIntArray(); childBank = in.readIntArray(); y = in.readFloatArray(); subEnabledCount = in.readByteArray(); enabledFlags = in.readByteArray(); dataStatic = in.readByteArray(); dataDirty = in.readByteArray(); error = in.readShortArray(); minY = in.readShortArray(); maxY = in.readShortArray(); } public void save( ScribeOutputStream out ) throws UnscribableNodeEncountered, IOException { out.writeInt( bankId ); out.writeInt( maxNodes ); out.writeInt( freeList ); out.writeInt( freeHandle ); out.writeIntArray( handles ); out.writeIntArray( next ); out.writeIntArray( child ); out.writeIntArray( childBank ); out.writeFloatArray( y ); out.writeByteArray( subEnabledCount ); out.writeByteArray( enabledFlags ); out.writeByteArray( dataStatic ); out.writeByteArray( dataDirty ); out.writeShortArray( error ); out.writeShortArray( minY ); out.writeShortArray( maxY ); } }