001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.vis.general; 034 035import java.io.IOException; 036import java.net.MalformedURLException; 037 038import javax.media.opengl.GL; 039import javax.media.opengl.GL2; 040import javax.media.opengl.GL2GL3; 041import javax.media.opengl.GLAutoDrawable; 042import javax.media.opengl.fixedfunc.GLLightingFunc; 043import javax.media.opengl.fixedfunc.GLMatrixFunc; 044 045import org.openimaj.image.FImage; 046import org.openimaj.image.colour.ColourMap; 047import org.openimaj.util.array.ArrayUtils; 048import org.openimaj.vis.Visualisation3D; 049 050/** 051 * Visualisation that draws a height map in 3d. 052 * 053 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 054 * @created 11 Jul 2013 055 * @version $Author$, $Revision$, $Date$ 056 */ 057public class HeightMap3D extends Visualisation3D<double[][]> 058{ 059 /** 060 * Rendering style for the height map. 061 * 062 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 063 * @created 12 Jul 2013 064 */ 065 public enum HeightMapType 066 { 067 /** Draw height map as points */ 068 POINTS, 069 070 /** Draw height map as lines */ 071 LINE, 072 073 /** Draw height map as wireframe */ 074 WIRE, 075 076 /** Draw height map as a solid */ 077 SOLID, 078 079 /** Draw solid and wire frame */ 080 SOLID_AND_WIRE, 081 082 /** Draw height map as a textured solid */ 083 TEXTURED 084 } 085 086 /** The way to draw the height map grid */ 087 private HeightMapType renderType = HeightMapType.SOLID_AND_WIRE; 088 089 /** The colour map to apply to the height field */ 090 private ColourMap colourMap = ColourMap.Autumn; 091 092 /** The colour of the solid if no colour map specified, or the colour of the wireframe */ 093 private float[] colour = new float[]{ 0f, 0f, 0f }; 094 095 /** The maximum expected data value */ 096 private double max = 1; 097 098 /** The minimum expected data value */ 099 private double min = 0; 100 101 /** Whether to auto scale so that the data fits in a cube (1 in the y axis) */ 102 private boolean autoScale = false; 103 104 /** The length of the patch to render */ 105 private double length = 1; 106 107 /** The width of the patch to render */ 108 private double width = 1; 109 110 /** Whether the camera needs to be reset on the next iteration */ 111 private boolean resetCamera = true; 112 113 /** 114 * @param width 115 * @param height 116 */ 117 public HeightMap3D( final int width, final int height ) 118 { 119 super( width, height ); 120 } 121 122 /** 123 * {@inheritDoc} 124 * 125 * @see org.openimaj.vis.VisualisationImageProvider#updateVis() 126 */ 127 @Override 128 public void updateVis() 129 { 130 } 131 132 /** 133 * {@inheritDoc} 134 * 135 * @see org.openimaj.vis.Visualisation3D#renderVis(javax.media.opengl.GLAutoDrawable) 136 */ 137 @Override 138 protected void renderVis( final GLAutoDrawable drawable ) 139 { 140 if( this.resetCamera ) 141 this.setupCamera(); 142 143 final GL2 gl = drawable.getGL().getGL2(); 144 145 gl.glPushMatrix(); 146 gl.glMatrixMode( GLMatrixFunc.GL_MODELVIEW ); 147 148 // Determine whether we're creating lines or quads 149 switch( this.renderType ) 150 { 151 case POINTS: 152 gl.glBegin( GL.GL_POINTS ); 153 break; 154 case LINE: 155 gl.glBegin( GL.GL_LINES ); 156 break; 157 case SOLID_AND_WIRE: 158 gl.glEnable( GL.GL_POLYGON_OFFSET_FILL ); 159 gl.glPolygonOffset( 1, 1 ); 160 case SOLID: 161 case TEXTURED: 162 gl.glBegin( GL2.GL_QUADS ); 163 gl.glPolygonMode( GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL ); 164 break; 165 case WIRE: 166 break; 167 default: 168 break; 169 } 170 171 this.renderHeightMap( gl ); 172 173 gl.glDisable( GL.GL_POLYGON_OFFSET_FILL ); 174 if( this.renderType == HeightMapType.SOLID_AND_WIRE ) 175 { 176 final ColourMap cm = this.colourMap; 177 this.colourMap = null; 178 this.colour = new float[] { 0f,0f,0f }; 179 180 gl.glEnd(); 181 gl.glBegin( GL.GL_LINE_STRIP ); 182 this.renderType = HeightMapType.WIRE; 183 184 gl.glDisable( GLLightingFunc.GL_LIGHTING ); 185 gl.glDisable( GLLightingFunc.GL_LIGHT0 ); 186 187 this.renderHeightMap( gl ); 188 189 this.renderType = HeightMapType.SOLID_AND_WIRE; 190 this.colourMap = cm; 191 } 192 193 gl.glEnd(); 194 195 // Reset stuff 196 gl.glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); 197// gl.glPolygonMode( GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL ); 198 199 gl.glPopMatrix(); 200 } 201 202 /** 203 * @see "http://azerdark.wordpress.com/2010/01/09/landscape-terrain-using-jogl/" 204 * @param gl 205 * @param pHeightMap 206 */ 207 private void renderHeightMap( final GL2 gl ) 208 { 209 if( this.data == null || this.data.length < 2 ) return; 210 211 // Determine the size of the grid from the first data element. 212 final int N = this.data.length; 213 final int M = this.data[0].length; 214 215 final double stepSizeX = this.length/N; 216 final double stepSizeY = -this.width/M; 217 218 for( int X = 0; X < N-1; X += 1 ) 219 { 220 if( this.renderType == HeightMapType.WIRE ) 221 gl.glBegin( GL.GL_LINE_STRIP ); 222 223 for( int Y = 0; Y < M-1; Y += 1 ) 224 { 225 // ---------------------------------------------- 226 this.createVertex( gl, N, M, stepSizeX, stepSizeY, X, Y ); 227 this.createVertex( gl, N, M, stepSizeX, stepSizeY, X, Y+1 ); 228 this.createVertex( gl, N, M, stepSizeX, stepSizeY, X+1, Y+1 ); 229 this.createVertex( gl, N, M, stepSizeX, stepSizeY, X+1, Y ); 230 // ---------------------------------------------- 231 232 if( this.renderType == HeightMapType.WIRE ) 233 this.createVertex( gl, N, M, stepSizeX, stepSizeY, X, Y ); 234 } 235 236 if( this.renderType == HeightMapType.WIRE ) 237 gl.glEnd(); 238 } 239 } 240 241 /** 242 * Creates a single colour mapped vertex. 243 * 244 * @param gl The GL context 245 * @param N The number of points in the X direction 246 * @param M The number of points in the Z direction 247 * @param stepSizeX The size of each quad in the patch 248 * @param stepSizeY The size of each quad in the patch 249 * @param X The index along the X direction 250 * @param Y The index along the Z direction (Y in the data) 251 */ 252 private void createVertex( final GL2 gl, final int N, final int M, 253 final double stepSizeX, final double stepSizeY, final int X, final int Y ) 254 { 255 final double x = X * stepSizeX; 256 final double z = Y * stepSizeY; 257 double y = this.data[Y][X]; 258 259 if( this.autoScale ) 260 y *= 1d/(this.max-this.min); 261 262 if( this.renderType == HeightMapType.TEXTURED ) 263 gl.glTexCoord2f( (float)(x / N), (float)(z / M) ); 264 else 265 { 266 final float[] c = this.colour; 267 if( this.colourMap != null ) 268 this.colourMap.apply( (float)(this.data[Y][X] * 1d/(this.max-this.min)), c ); 269 270 gl.glColor3f( c[0], c[1], c[2] ); 271 } 272 gl.glVertex3d( x, y, z ); 273 } 274 275 /** 276 * {@inheritDoc} 277 * 278 * @see javax.media.opengl.GLEventListener#init(javax.media.opengl.GLAutoDrawable) 279 */ 280 @Override 281 public void init( final GLAutoDrawable drawable ) 282 { 283 super.init( drawable ); 284 this.setupCamera(); 285 } 286 287 /** 288 * Set the camera to be pointing to the middle of the height map 289 */ 290 private void setupCamera() 291 { 292 System.out.println( "Resetting camera: "+this.length+", "+this.width ); 293 294 // Set the initial look at 295 final float eyeX = 0.5f, eyeY = 1f, eyeZ = 0f; 296 final float lookAtX = (float)this.length/2, lookAtY = 0f, lookAtZ = (float)-this.width/2; 297 final float upX = 0, upY = 1, upZ = 0; 298 this.glu.gluLookAt( eyeX, eyeY, eyeZ, lookAtX, lookAtY, lookAtZ, upX, upY, upZ ); 299 300 // Instantiate the camera mover 301 this.cameraPosition = new RotatingCameraProvider( 302 eyeX, eyeY, eyeZ, 303 lookAtX, lookAtY, lookAtZ, 304 0.0004f, 0.0001f, 0.0002f, 0.75f, 0.75f, 1f ); 305 306 this.resetCamera = false; 307 } 308 309 /** 310 * @return the colourMap 311 */ 312 public ColourMap getColourMap() 313 { 314 return this.colourMap; 315 } 316 317 /** 318 * @param colourMap the colourMap to set 319 */ 320 public void setColourMap( final ColourMap colourMap ) 321 { 322 this.colourMap = colourMap; 323 } 324 325 /** 326 * Set the maximum value expected in this heightmap. 327 * @param max The maximum value 328 */ 329 public void setMaxValue( final double max ) 330 { 331 this.max = max; 332 } 333 334 /** 335 * Set the minimum value expected in this heightmap; 336 * @param min The minimum value 337 */ 338 public void setMinValue( final double min ) 339 { 340 this.min = min; 341 } 342 343 /** 344 * Set whether to fit the maximum possible value into the height map cube. 345 * @param autoScale TRUE to autoscale. 346 */ 347 public void setAutoScale( final boolean autoScale ) 348 { 349 this.autoScale = autoScale; 350 } 351 352 /** 353 * Set the length of the rendered map 354 * @param l The length 355 */ 356 public void setLength( final double l ) 357 { 358 this.length = l; 359 this.resetCamera = true; 360 } 361 362 /** 363 * Set the width of the rendered map 364 * @param w the width 365 */ 366 public void setWidth( final double w ) 367 { 368 this.width = w; 369 this.resetCamera = true; 370 } 371 372 /** 373 * Set the way the height map is rendered. 374 * @param type The type 375 */ 376 public void setHeightMapType( final HeightMapType type ) 377 { 378 this.renderType = type; 379 } 380 381 /** 382 * Create a height map from an FImage. 383 * @param img The image 384 * @return The height map 385 */ 386 public static HeightMap3D createFromFImage( final FImage img ) 387 { 388 final HeightMap3D hm = new HeightMap3D( img.getWidth(), img.getHeight() ); 389 hm.setMaxValue( 10 ); 390 hm.setMinValue( 0 ); 391 hm.setData( ArrayUtils.convertToDouble( img.pixels ) ); 392 return hm; 393 } 394 395 /** 396 * Example of the height map. Switches between showing a 2D Gaussian and a 2D sinc 397 * function. 398 * 399 * @param args 400 * @throws IOException 401 * @throws MalformedURLException 402 * @throws InterruptedException 403 */ 404 public static void main( final String[] args ) throws MalformedURLException, 405 IOException, InterruptedException 406 { 407// HeightMap3D.createFromFImage( ImageUtilities.readF( 408// new URL("http://www.alvaromartin.net/images/surfaceclipmaps/heightmap.jpg") ) ); 409 410 final HeightMap3D hm = new HeightMap3D( 1000, 800 ); 411 hm.setEnableLights( false ); 412 hm.setMaxValue( 1 ); 413 hm.setMinValue( -0.2 ); 414 415 final double[][] sinc = HeightMap3D.getSinc( 60, 60, 120 ); 416 final double[][] gauss = HeightMap3D.getGaussian( 60, 60 ); 417 418 boolean showingSinc = true; 419 hm.setData( sinc ); 420 421 while( true ) 422 { 423 showingSinc = !showingSinc; 424 425 Thread.sleep( 2000 ); 426 427 if( showingSinc ) 428 hm.setData( sinc ); 429 else hm.setData( gauss ); 430 } 431 } 432 433 /** 434 * Helper function that's used to draw a 2d Gaussian function 435 * @param N The number of patches in X 436 * @param M The number of patches in Z 437 * @return The Gaussian data 438 */ 439 private static double[][] getGaussian( final int N, final int M ) 440 { 441 final double[][] g = new double[M][N]; 442 443 final double xo = N/2d; 444 final double yo = M/2d; 445 final double sx = N/12d; 446 final double sy = M/12d; 447 final double A = 0.7; 448 449 for( int y = 0; y < M; y++ ) 450 { 451 for( int x = 0; x < N; x++ ) 452 { 453 g[y][x] = A * Math.exp( - (Math.pow(x-xo,2) / Math.pow(2*sx,2) 454 + Math.pow(y-yo,2) / Math.pow(2*sy,2) )); 455 } 456 } 457 458 return g; 459 } 460 461 /** 462 * Helper function that's used to draw a 2d sinc function 463 * @param N The number of patches in X 464 * @param M The number of patches in Z 465 * @param scalar The scalar for the sinc function 466 * @return The sinc data 467 */ 468 private static double[][] getSinc( final int N, final int M, final double scalar ) 469 { 470 final double[][] s = new double[M][N]; 471 472 final double scalex = N/scalar; 473 final double scaley = M/scalar; 474 475 for( int y = 0; y < M; y++ ) 476 { 477 for( int x = 0; x < N; x++ ) 478 { 479 double xx = x - N/2d; 480 double yy = y - M/2d; 481 xx *= scalex; 482 yy *= scaley; 483 484 double vx = Math.sin(xx)/xx; 485 if( xx == 0 ) vx = 1; 486 double vy = Math.sin(yy)/yy; 487 if( yy == 0 ) vy = 1; 488 489 s[y][x] = vx * vy; 490 } 491 } 492 493 return s; 494 } 495}