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}