/** * 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.heightmap; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.BufferedWriter; import java.io.IOException; import java.io.Serializable; import java.io.StringWriter; import org.openmali.vecmath2.Point3f; import org.xith3d.scenegraph.Geometry; import org.xith3d.scenegraph.IndexedTriangleArray; /** * Storage and conversion for height maps (two dimentional arrays of height data). * The height maps can be created in many ways such as a random generator, or reading * an image. This class allows the 2D height map to be exported to ASCII (for debuggin), * 2D images (for terrain detailing and cloud textures) and 3D geometry (for intereactive * terrain). * * @author William Denniss * @version 1.0 - 22 December 2003 */ public class HeightMap implements Serializable { private static final long serialVersionUID = -3199715473452121495L; /** * Alternate rows when exporting 3D triangular geometry (default) */ public final static int ZIGZAG_ROWS = 1; /** * Alternate collumns when exporting 3D triangular geometry. */ public final static int ZIGZAG_COLS = 2; /** * Changes starting * */ public final static int FLIP_STARTING = 4; // The heightmap data protected float[][] heightmap; /** * Empty Constructor */ protected HeightMap() { } /** * Initialises using the given two dimentional array as the hight map. * * @param heightmap the heightmap to be used */ public HeightMap( float[][] heightmap ) { this.heightmap = heightmap; } /** * Initialises using the given one dimentional array, unfolded using * the given node count per side. * * @param heights the table of heights to be used * @param nodesPerSide width of table in number of cells. */ public HeightMap( float[] heights, int nodesPerSide ) { heightmap = new float[ nodesPerSide ][ heights.length / nodesPerSide ]; for ( int i = 0; i < heights.length; i++ ) { heightmap[ i % nodesPerSide ][ i / nodesPerSide ] = heights[ i ]; } } /** * @return string representation of this heightmap. */ public String generateUTF() { StringWriter sw = new StringWriter(); try { BufferedWriter bw = new BufferedWriter( sw ); for ( int i = 0; i < heightmap.length; i++ ) { for ( int j = 0; j < heightmap[ i ].length; j++ ) { bw.write( (int)Math.abs( heightmap[ i ][ j ] ) ); } bw.newLine(); } } catch ( IOException e ) { throw new RuntimeException( e ); } return ( sw.toString() ); } /** * @return graphical grayscale image representation of this heightmap */ public BufferedImage generate2D() { BufferedImage terrainmap = new BufferedImage( heightmap.length, heightmap[ 0 ].length, BufferedImage.TYPE_INT_RGB ); Graphics2D g = terrainmap.createGraphics(); for ( int i = 0; i < heightmap.length; i++ ) { for ( int j = 0; j < heightmap[ i ].length; j++ ) { int rgb = (int)heightmap[ i ][ j ] + 128; if ( rgb < 0 ) rgb = 0; if ( rgb > 255 ) rgb = 255; g.setColor( new Color( rgb, rgb, rgb ) ); g.fillRect( i, j, 1, 1 ); } } return ( terrainmap ); } /** * Builds 3D geometrical representation of this height map using the given offsets and spacing. * * @param startX starting X value (used to offset the geometry) * @param startY starting Y value (used to offset the geometry) * @param stepX distance along the X-axis between vertexes * @param stepY distance along the Y-axis between vertexes */ public Geometry generate3D( float startX, float startY, float stepX, float stepY ) { return ( generate3D( startX, startY, stepX, stepY, ZIGZAG_ROWS ) ); } /** * Builds 3D geometrical representation of this height map using the given offsets and spacing. * internally calls the relevent tesselate for the geometry you have specified. * * @param startX starting X value (used to offset the geometry) * @param startY starting Y value (used to offset the geometry) * @param stepX distance along the X-axis between vertexes * @param stepY distance along the Y-axis between vertexes * @param flags modifiers including using Triangles instead of Quads to generate the geom. */ public IndexedTriangleArray generate3D( float startX, float startY, float stepX, float stepY, int flags ) { // Generates the vertexes from the heightmap Point3f[][] coords = calculateCoords( startX, startY, stepX, stepY ); Point3f[] verticies = flatten2DArray( coords ); int[] indices = calculateIndicies( coords.length, coords[ 0 ].length, flags ); // Calculates the coordinate array from the vertex indicies IndexedTriangleArray ita = new IndexedTriangleArray( verticies.length, indices.length ); ita.setCoordinates( 0, verticies ); ita.setIndex( indices ); ita.setInitialIndexIndex( 0 ); ita.calculateFaceNormals(); ita.setIndex( indices ); ita.setValidIndexCount( indices.length ); ita.calculateFaceNormals(); return ( ita ); } /** * Creates Odejava GeomTriMesh object */ /* public GeomTriMesh generateOde (String name, float startX, float startY, float stepX, float stepY, int flags) { Point3f[][] coords = calculateCoords( startX, startY, stepX, stepY ); float[] vertices = point2float( flatten2DArray( coords ) ); int[] indices = calculateIndicies( coords.length, coords[ 0 ].length, flags ); return ( new GeomTriMesh( name, vertices, indices ) ); } public static GeomTriMesh generateOde (IndexedTriangleArray ita) { return ( Xith3DToOdejava.createTriMesh( ita ) ); } */ /** * Calculates vertex points using given offsets and spacing. * * @param startX starting X value (used to offset the geometry) * @param startY starting Y value (used to offset the geometry) * @param stepX distance along the X-axis between vertexes * @param stepY distance along the Y-axis between vertexes */ public Point3f[][] calculateCoords( float startX, float startY, float stepX, float stepY ) { // Generates the vertexes from the heightmap Point3f[][] pts = new Point3f[ heightmap.length ][ heightmap[ 0 ].length ]; for ( int i = 0; i < heightmap.length; i++ ) { for ( int j = 0; j < heightmap[ i ].length; j++ ) { pts[ i ][ j ] = new Point3f( startX + stepX * i, startY + stepY * j, heightmap[ i ][ j ] ); } } return ( pts ); } public static Point3f[] unIndex( Point3f[] vertices, int[] indices ) { Point3f[] points = new Point3f[ indices.length ]; for ( int i = 0; i < indices.length; i++ ) { points[ i ] = vertices[ indices[ i ] ]; } return ( points ); } /** * Converts the two dimentional table of points into 3D geometry by calculating the connecting edges * between the verticies by using Triangular primitives. * * @param width * @param height * @param flags modifiers * * @return built geometry * * @see #ZIGZAG_ROWS * @see #ZIGZAG_COLS * @see #generate3D (float startX, float startY, float stepX, float stepY, int flags) */ public static int[] calculateIndicies( int width, int height, int flags ) { int[] indices = new int[ ( width - 1 ) * ( height - 1 ) * 6 ]; int alternate = 0; if ( ( flags & FLIP_STARTING ) == FLIP_STARTING ) { alternate = 1; } int indicies = 0; for ( int i = 0; i < width - 1; i++ ) { for ( int j = 0; j < height - 1; j++ ) { if ( alternate == 0 ) { indices[ indicies++ ] = flattenCoordinate( i + 1, j, width ); indices[ indicies++ ] = flattenCoordinate( i, j, width ); indices[ indicies++ ] = flattenCoordinate( i, j + 1, width ); indices[ indicies++ ] = flattenCoordinate( i + 1, j + 1, width ); indices[ indicies++ ] = flattenCoordinate( i + 1, j, width ); indices[ indicies++ ] = flattenCoordinate( i, j + 1, width ); } else { indices[ indicies++ ] = flattenCoordinate( i + 1, j + 1, width ); indices[ indicies++ ] = flattenCoordinate( i, j, width ); indices[ indicies++ ] = flattenCoordinate( i, j + 1, width ); indices[ indicies++ ] = flattenCoordinate( i + 1, j + 1, width ); indices[ indicies++ ] = flattenCoordinate( i + 1, j, width ); indices[ indicies++ ] = flattenCoordinate( i, j, width ); } if ( ( flags & ZIGZAG_COLS ) == ZIGZAG_COLS ) { alternate++; alternate = alternate % 2; } } if ( ( flags & ZIGZAG_ROWS ) == ZIGZAG_ROWS ) { alternate++; alternate = alternate % 2; } } return ( indices ); } private static int flattenCoordinate( int x, int y, int width ) { return ( y * width + x ); } public static Point3f[] flatten2DArray( Point3f[][] vertices ) { Point3f[] vert2 = new Point3f[ vertices.length * vertices[ 0 ].length ]; int vertCount = 0; for ( int i = 0; i < vertices.length; i++ ) { for ( int j = 0; j < vertices[ i ].length; j++ ) { vert2[ vertCount++ ] = vertices[ i ][ j ]; } } return ( vert2 ); } public static float[] point2float( Point3f[] coords ) { float[] vertices = new float[ coords.length * 3 ]; int vertex = 0; for ( int i = 0; i < coords.length; i++ ) { vertices[ vertex++ ] = coords[ i ].getX(); vertices[ vertex++ ] = coords[ i ].getY(); vertices[ vertex++ ] = coords[ i ].getZ(); } return ( vertices ); } }