/*
 * Copyright (c) 2003, jMonkeyEngine - Mojo Monkey Coding
 * All rights reserved.
 *
 * 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 Mojo Monkey Coding, jME, jMonkey Engine, 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) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

package jme.locale.external.data;

import java.util.logging.Level;

import jme.exception.MonkeyRuntimeException;
import jme.utility.LoggingSystem;

/**
 * <code>MidPointHeightMap</code creates a heightmap based on the 
 * Midpoint Displacement fractal generation algorithm based on Jason Shankel's
 * paper from "Game Programming Gems". Terrain is generated by displacing the
 * center of a "square" multiple times. Displacing the center creates for 
 * new squares, these new squares are then treated the same way, until the level
 * of detail has been reached.
 * 
 * It is important to note that the size of the terrain MUST be a power of
 * two.
 * 
 * @author Mark Powell
 * @version 1
 */
public class MidPointHeightMap extends AbstractHeightMap {
    private float roughness;

	/**
	 * Constructor builds a new heightmap using the midpoint displacement
	 * algorithm. Roughness determines how chaotic the terrain will be. 
	 * Where 1 is perfectly self-similar, > 1 early iterations have a 
	 * disproportionately large effect creating smooth terrain, and < 1 
	 * late iteraions have a disproportionately large effect creating 
	 * chaotic terrain.
	 * 
	 * @param size the size of the terrain, must be a power of 2.
	 * @param roughness how chaotic to make the terrain.
	 * 
	 * @throws MonkeyRuntimeException if size is less than or equal to
	 * 		zero or roughtness is less than 0.
	 */
    public MidPointHeightMap(int size, float roughness) {
        if (roughness < 0 || size <= 0) {
            throw new MonkeyRuntimeException("size and roughness must be " +
            	"greater than 0");
        }
        this.roughness = roughness;
        this.size = size;
        
        load();
    }

	/**
     * <code>load</code> generates the heightfield using the Midpoint Displacement
     * algorithm. <code>load</code> uses the latest attributes, so a call
     * to <code>load</code> is recommended if attributes have changed using
     * the set methods.
     */
    public boolean load() {
        float height;
        double heightReducer;
        float[][] tempBuffer;

		//holds the points of the square.
        int ni, nj;
        int mi, mj;
        int pmi, pmj;

        if (null != heightData) {
            unloadHeightMap();
        }
        
        height = size / 2;
        heightReducer = Math.pow(2, -1 * roughness);

        heightData = new int[size][size];
        tempBuffer = new float[size][size];

        int counter = size;
        while (counter > 0) {
            //displace the center of the square.
            for (int i = 0; i < size; i += counter) {
                for (int j = 0; j < size; j += counter) {
                	//(0,0) point of the local square
                    ni = (i + counter) % size;
                    nj = (j + counter) % size;
					//middle point of the local square
                    mi = (i + counter / 2);
                    mj = (j + counter / 2);

					//displayce the middle point by the average of the 
					//corners, and a random value.
                    tempBuffer[mi][mj] =
                        (float) ((tempBuffer[i][j]
                            + tempBuffer[ni][j]
                            + tempBuffer[i][nj]
                            + tempBuffer[ni][nj])
                            / 4
                            + (Math.random() * height - (height / 2)));
                }
            }

			//next calculate the new midpoints of the line segments.
            for (int i = 0; i < size; i += counter) {
                for (int j = 0; j < size; j += counter) {
                	//(0,0) of the local square
                    ni = (i + counter) % size;
                    nj = (j + counter) % size;

					//middle point of the local square.
                    mi = (i + counter / 2);
                    mj = (j + counter / 2);

					//middle point on the line in the x-axis direction.
                    pmi = (i - counter / 2 + size) % size;
                    //middle point on the line in the y-axis direction.
                    pmj = (j - counter / 2 + size) % size;

                    //Calculate the square value for the top side of the rectangle
                    tempBuffer[mi][j] =
                        (float) ((tempBuffer[i][j]
                            + tempBuffer[ni][j]
                            + tempBuffer[mi][pmj]
                            + tempBuffer[mi][mj])
                            / 4
                            + (Math.random() * height - (height / 2)));

                    //Calculate the square value for the left side of the rectangle
                    tempBuffer[i][mj] =
                        (float) ((tempBuffer[i][j]
                            + tempBuffer[i][nj]
                            + tempBuffer[pmi][mj]
                            + tempBuffer[mi][mj])
                            / 4
                            + (Math.random() * height - (height / 2)));

                }
            }

            counter /= 2;
            height *= heightReducer;
        }

        normalizeTerrain(tempBuffer);

        //transfer the new terrain into the height map.
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                setHeightAtPoint((int)tempBuffer[i][j], i, j);
            }
        }

        LoggingSystem.getLoggingSystem().getLogger().log(
            Level.INFO,
            "Created Heightmap using Fault Fractal");
        return true;
    }

	/**
	 * <code>setRoughness</code> sets the new roughness value of the
	 * heightmap. Roughness determines how chaotic the terrain will be. 
	 * Where 1 is perfectly self-similar, > 1 early iterations have a 
	 * disproportionately large effect creating smooth terrain, and < 1 
	 * late iteraions have a disproportionately large effect creating 
	 * chaotic terrain.
	 * 
	 * @param roughness how chaotic will the heightmap be.
	 */
    public void setRoughness(float roughness) {
        if (roughness < 0) {
            throw new MonkeyRuntimeException("roughness must be greater than 0");
        }
        this.roughness = roughness;
    }

   
}
