Package dwlab.shapes.maps

Source Code of dwlab.shapes.maps.DoubleMap

/* Digital Wizard's Lab - game development framework
* Copyright (C) 2013, Matt Merkulov

* All rights reserved. Use of this code is allowed under the
* Artistic License 2.0 terms, as specified in the license.txt
* file distributed with this code, or available from
* http://www.opensource.org/licenses/artistic-license-2.0.php */

package dwlab.shapes.maps;

import dwlab.base.images.Image;
import dwlab.base.service.Service;
import dwlab.base.images.ImageBuffer;
import static dwlab.platform.Functions.*;

/**
* DoubleMap is basicaly a heightmap.
* It is 2d array of Double values which are in 0.0...1.0 interval.
*/
public class DoubleMap extends Map {
  public enum Channel {
    /**
    * Constant for filling red color channel in pixmap.
    * @see #pasteToImage, #pasteToPixmap
    */
    RED,
   
    /**
    * Constant for filling green color channel in pixmap.
    * @see #pasteToImage, #pasteToPixmap
    */
    GREEN,

    /**
    * Constant for filling blue color channel in pixmap.
    * @see #pasteToImage, #pasteToPixmap
    */
    BLUE,

    /**
    * Constant for filling alpha channel in pixmap (transparency).
    * @see #pasteToImage, #pasteToPixmap
    */
    ALPHA,

    /**
    * Constant for filling all color channels in pixmap (resulting color will be from black to white).
    * @see #pasteToImage, #pasteToPixmap
    */
    RGB
  }
 
  public enum PasteMode {
    /**
    * Constant for overwriting source heightmap values by destination heightmap values.
    * @see #overwrite, #add, #multiply, #maximum, #minimum, #paste
    */
    OVERWRITE,

    /**
    * Constant for adding source heightmap values to destination heightmap values.
    * @see #overwrite, #add, #multiply, #maximum, #minimum, #paste, #limit
    */
    ADD,

    /**
    * Constant for multiplying source heightmap values by destination heightmap values.
    * @see #overwrite, #add, #multiply, #maximum, #minimum, #paste
    */
    MULTIPLY,

    /**
    * Constant for selecting maximum value between source heightmap values and destination heightmap values.
    * @see #overwrite, #add, #multiply, #maximum, #minimum, #paste
    */
    MAXIMUM,

    /**
    * Constant for selecting minimum value between source heightmap values and destination heightmap values.
    * @see #overwrite, #add, #multiply, #maximum, #minimum, #paste
    */
    MINIMUM
  }


  /**
   * Array of heightmap values.
   */
  public double value[ ][ ];

  // ==================== Creating ===================

  /**
   * Creates double map using given resolution.
   * @return Created double map.
   * @see #paste example
   */
  public DoubleMap( int xQuantity, int yQuantity ) {
    setResolution( xQuantity, yQuantity );
  }

  // ==================== Parameters ====================

  @Override
  public final void setResolution( int newXQuantity, int newYQuantity ) {
    super.setResolution( newXQuantity, newYQuantity );
    value = new double[ newYQuantity ][];
    for( int yy = 0; yy < newYQuantity; yy++ ) value[ yy ] = new double[ newXQuantity ];
  }

  // ==================== Manipulations ==================== 

  /**
   * Converts heightmap to new image with single frame.
   * @return New image.
   * By default every color channel will be filled by heightmap values, but you can specify another channel filling mode.
   *
   * @see #toNewPixmap, #pasteToImage, #pasteToPixmap, #paste example
   */
 
  public ImageBuffer toNewImageBuffer( Channel channel ) {
    ImageBuffer buffer = new ImageBuffer( xQuantity, yQuantity );
    buffer.clear( 0xFF );
    paste( buffer, 0, 0, 0, channel );
    return buffer;
  }
 
  public ImageBuffer toNewImageBuffer() {
    return toNewImageBuffer( Channel.RGB );
  }

  public Image toNewImage( Channel channel ) {
    return toNewImageBuffer( channel ).toImage();
  }
 
  public Image toNewImage() {
    return toNewImage( Channel.RGB );
  }


  /**
   * Pastes heightmap to existing image frame with given shift.
   * By default every color channel will be filled by heightmap values, but you can specify another channel filling mode.
   *
   * @see #toNewImage, #toNewPixmap, #pasteToPixmap, #paste example
   */
  public void paste( ImageBuffer buffer, int frame, int xShift, int yShift, Channel channel ) {
    for( int y1=0; y1 < yQuantity; y1++ ) {
      for( int x1=0; x1 < xQuantity; x1++ ) {
        int col = (int) Math.round( 255d * value[ y1 ][ x1 ] );

        int x2, y2;
        if( masked ) {
          x2 = ( x1 + xShift ) & xMask;
          y2 = ( y1 + yShift ) & yMask;
        } else {
          x2 = wrapX( x1 + xShift );
          y2 = wrapY( y1 + yShift );
        }

        int pixel = buffer.getPixel( x2, y2 );

        switch( channel ) {
          case RGB:
            buffer.setPixel( x2, y2, ( col * 0x01010100 ) | ( pixel & 0xFF )  );
            break;
          case ALPHA:
            buffer.setPixel( x2, y2, col | ( pixel & 0xFFFFFF00 )  );
            break;
          case BLUE:
            buffer.setPixel( x2, y2, ( col << 8 ) | ( pixel & 0xFFFF00FF )  );
            break;
          case GREEN:
            buffer.setPixel( x2, y2, ( col << 16 ) | ( pixel & 0xFF00FFFF )  );
            break;
          case RED:
            buffer.setPixel( x2, y2, ( col << 24 ) | ( pixel & 0x00FFFFFF )  );
            break;
        }
      }
    }
  }
 
  public void paste( ImageBuffer buffer, Channel channel ) {
    paste( buffer, 0, 0, 0, channel );
  }


  /**
   * Pastes one heightmap over another.
   * You can change coordinate shift and pasting mode.
   * All parts of source heightmap which will be outside destination pixmap will be wrapped around destination pixmap.
   *
   * @see #overwrite, #add, #multiply, #maximum, #minimum
   */
  public void paste( DoubleMap sourceMap, int xx, int yy, PasteMode mode ) {
    for( int y0=0; y0 < sourceMap.yQuantity; y0++ ) {
      for( int x0=0; x0 < sourceMap.xQuantity; x0++ ) {
        int x1, y1;

        if( masked ) {
          x1 = ( xx + x0 ) & xMask;
          y1 = ( yy + y0 ) & yMask;
        } else {
          x1 = wrapX( xx + x0 );
          y1 = wrapY( yy + y0 );
        }

        switch( mode ) {
          case OVERWRITE:
            value[ y1 ][ x1 ] = sourceMap.value[ y0 ][ x0 ];
            break;
          case ADD:
            value[ y1 ][ x1 ] = value[ y1 ][ x1 ] + sourceMap.value[ y0 ][ x0 ];
            break;
          case MULTIPLY:
            value[ y1 ][ x1 ] = value[ y1 ][ x1 ] * sourceMap.value[ y0 ][ x0 ];
            break;
          case MAXIMUM:
            value[ y1 ][ x1 ] = Math.max( value[ y1 ][ x1 ], sourceMap.value[ y0 ][ x0 ] );
            break;
          case MINIMUM:
            value[ y1 ][ x1 ] = Math.min( value[ y1 ][ x1 ], sourceMap.value[ y0 ][ x0 ] );
            break;
        }
      }
    }
  }

  public void paste( DoubleMap sourceMap, PasteMode mode ) {
    paste( sourceMap, 0, 0, mode );
  }
 
  public void paste( DoubleMap sourceMap ) {
    paste( sourceMap, 0, 0, PasteMode.ADD );
  }
 

  /**
   * Extracts slice of heightmap to the tilemap.
   * Areas of Tilemap with corresponding heightmap values between VFrom and VTo will be filled with TileNum tile index.
   * Tilemap and heightmap must have same resolution.
   *
   * @see #enframe example
   */
  public void extractTo( IntMap tileMap, double vFrom, double vTo, int tileNum ) {
    if( debug ) if( tileMap.xQuantity != xQuantity || tileMap.yQuantity != yQuantity ) {
      error( "Sizes of source heightmap and resulting tilemap are different." );
    }

    for( int yy=0; yy < yQuantity; yy++ ) {
      for( int xx=0; xx < xQuantity; xx++ ) {
        if( value[ yy ][ xx ] >= vFrom && value[ yy ][ xx ] < vTo ) tileMap.value[ yy ][ xx ] = tileNum;
      }
    }
  }


  /**
   * Blurs the heightmap with simple 3x3 filter.
   * @see #perlinNoise, #drawCircle example
   */
  public void blur() {
    double[][] newArray = new double[ yQuantity ][];

    for( int y0 = 0; y0 < yQuantity; y0++ ) {
      newArray[ y0 ] = new double[ xQuantity ];
      for( int x0=0; x0 < xQuantity; x0++ ) {
        double sum = 0;
        for( int xX=-1; xX <= 1; xX++ ) {
          for( int yY=-1; yY <= 1; yY++ ) {
            if( masked ) {
              sum += value[ ( y0 + yY ) & yMask ][ ( x0 + xX ) & xMask ];
            } else {
              sum += value[ wrapY( y0 + yY ) ][ wrapX( x0 + xX ) ];
            }
          }
        }
        newArray[ y0 ][ x0 ] = ( sum + value[ y0 ][ x0 ] * 7.0 ) / 16.0;
      }
    }
    value = newArray;
  }


  /**
   * Fills heightmap with perlin noise.
   * @see #blur, #enframe example
   */
  public void perlinNoise( int startingXFrequency, int startingYFrequency, double startingAmplitude, double dAmplitude, int layersQuantity ) {
    int xFrequency = startingXFrequency;
    int yFrequency = startingYFrequency;
    double amplitude = startingAmplitude;

    for( int xx = 0; xx <  xQuantity; xx++ ) {
      for( int yy = 0; yy <  yQuantity; yy++ ) {
        value[ yy ][ xx ] = 0.5d;
      }
    }

    for( int n=1; n <= layersQuantity; n++ ) {
      double array[][] = new double[ yFrequency ][];

      for( int aY = 0; aY <  yFrequency; aY++ ) {
        array[ aY ] = new double[ xFrequency ];
        for( int aX = 0; aX <  xFrequency; aX++ ) {
          array[ aY ][ aX ] = Service.random( -amplitude, amplitude );
        }
      }

      int fXMask = xFrequency - 1;
      int fYMask = yFrequency - 1;

      double kX = 1.0 * xFrequency / xQuantity;
      double kY = 1.0 * yFrequency / yQuantity;

      for( int yy = 0; yy <  yQuantity; yy++ ) {
        for( int xx = 0; xx <  xQuantity; xx++ ) {
          double xK = kX * xx;
          double yK = kY * yy;
         
          int arrayX = Service.floor( xK );
          int arrayY = Service.floor( yK );

          xK = ( 1.0 - Math.cos( 180.0 * ( xK - arrayX ) ) ) * 0.5;
          yK = ( 1.0 - Math.cos( 180.0 * ( yK - arrayY ) ) ) * 0.5;

          double z00 = array[ arrayY ][ arrayX ] ;
          double z10 = array[ arrayY ][ ( arrayX + 1 ) & fXMask ] ;
          double z01 = array[ ( arrayY + 1 ) & fYMask ][ arrayX ] ;
          double z11 = array[ ( arrayY + 1 ) & fYMask ][ ( arrayX + 1 ) & fXMask ] ;

          double z0 = z00 + ( z10 - z00 ) * xK;
          double z1 = z01 + ( z11 - z01 ) * xK;

          value[ yy ][ xx ] = value[ yy ][ xx ] + z0 + ( z1 - z0 ) * yK;
        }
      }

      xFrequency = 2 * xFrequency;
      yFrequency = 2 * yFrequency;
      amplitude = amplitude * dAmplitude;
    }

    limit();
  }


  public final double circleBound = 0.707107d;

  /**
   * Draws anti-aliased circle on the heightmap.
   * Parts of circle which will be ouside the heightmap, will be wrapped around.
   *
   * @see #paste example
   */
  public void drawCircle( double xCenter, double yCenter, double radius, double color ) {
    for( int y0 = Service.floor( yCenter - radius ); y0 < Math.ceil( yCenter + radius ); y0++ ) {
      for( int x0 = Service.floor( xCenter - radius ); x0 < Math.ceil( xCenter + radius ); x0++ ) {
        int xx, yy;
        if( masked ) {
          xx = x0 & xMask;
          yy = y0 & yMask;
        } else {
          xx = wrapX( x0 );
          yy = wrapY( y0 );
        }

        double dist = radius - Math.sqrt( ( x0 - xCenter ) * ( x0 - xCenter ) + ( y0 - yCenter ) * ( y0 - yCenter ) );

        if( dist > circleBound ) {
          value[ yy ][ xx ] = color;
        } else if( dist < -circleBound ) {
        } else {
          double x1 = x0 - 0.5d - xCenter;
          double y1 = y0 - 0.5d - yCenter;
          double x2 = x0 + 0.5d - xCenter;
          double y2 = y0 + 0.5d - yCenter;
          double dist00 = radius - Math.sqrt( x1 * x1 + y1 * y1 );
          double dist01 = radius - Math.sqrt( x1 * x1 + y2 * y2 );
          double dist10 = radius - Math.sqrt( x2 * x2 + y1 * y1 );
          double dist11 = radius - Math.sqrt( x2 * x2 + y2 * y2 );
          double k = Service.limit( 0.125d / circleBound * ( dist00 + dist01 + dist10 + dist11 ) + 0.5d, 0d, 1d );
          value[ yy ][ xx ] = value[ yy ][ xx ] * ( 1d - k ) + k * color;
        }
      }
    }
  }


  /**
   * Limits the heightmap values by standard interval.
   * @return
   * This method will force heighmap values to be in 0.0...1.0 interval.
   * Use this method after applying unsafe operations on heightmap, for example adding another heightmap to it.
   *
   * @see #paste example
   */
  public void limit() {
    for( int yy = 0; yy <  yQuantity; yy++ ) {
      for( int xx = 0; xx <  xQuantity; xx++ ) {
        if( value[ yy ][ xx ] < 0.0 ) value[ yy ][ xx ] = 0d;
        if( value[ yy ][ xx ] > 1.0 ) value[ yy ][ xx ] = 1d;
      }
    }
  }
}
TOP

Related Classes of dwlab.shapes.maps.DoubleMap

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.