/*
 * 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>FaultFractalHeightMap</code> creates a heightmap based on the 
 * Fault Formation fractal generation algorithm based on Jason Shankel's
 * paper from "Game Programming Gems". Terrain is generated by simulating
 * Earth quakes, where a random line is drawn through the heightmap and
 * one side is lifted by a factor. This is then run through a FIR filter
 * to simulate water errosion.
 * 
 * @author Mark Powell
 * @version 1
 */
public class FaultFractalHeightMap extends AbstractHeightMap {

	//Attributes of the fractal.
	private int iterations; //how many faults to generate.
	private int minDelta; //the range of height increases (min)
	private int maxDelta; //the range of height increases (max)
	
	/**
	 * Constructor sets the attributes of the fault fractal system and
	 * generates the heightmap.
	 * 
	 * @param size the size of the terrain to be generated.
	 * @param iterations the number of faults to generate.
	 * @param minDelta the minimum varience in the height increase between
	 *      faults.
	 * @param maxDelta the maximum varience in the height increase between
	 *      faults.
	 * @param filter the filter used for erosion. Filter can be between 0 and 
	 *      1, where 0 is no filtering and 1 is extreme filtering. Suggested
	 *      values are 0.2-0.4.
	 * 
	 * @throws MonkeyRuntimeException if size is not greater than zero, 
	 *      iterations is not greater than zero, minDelta is more than 
	 *      maxDelta and if filter is not between 0 and 1.
	 */
	public FaultFractalHeightMap(
		int size,
		int iterations,
		int minDelta,
		int maxDelta,
		float filter) {

		if (size <= 0
			|| iterations <= 0
			|| minDelta > maxDelta
			|| (filter < 0 || filter >= 1)) {
			throw new MonkeyRuntimeException(
				"Either size is not greater than"
					+ " zero, iterations is not greater than zero, minDelta is more "
					+ "than maxDelta and/or filter is not between 0 and 1.");
		}

		this.size = size;
		this.iterations = iterations;
		this.minDelta = minDelta;
		this.maxDelta = maxDelta;
		this.filter = filter;

		load();
	}

	/**
	 * <code>load</code> generates the heightfield using the Fault Fractal
	 * 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() {
		//amount to raise a slice of terrain.
		float heightVarience;
		//random points for the fault line
		int randomX1, randomZ1;
		int randomX2, randomZ2;
		//line directions
		int directionX1, directionZ1;
		int directionX2, directionZ2;

		//clean up data if needed.
		if (null != heightData) {
			unloadHeightMap();
		}

		//allocate new arrays
		heightData = new int[size][size];
		float[][] tempBuffer = new float[size][size];

		//generate faults for the number of iterations given.
		for (int i = 0; i < iterations; i++) {
			heightVarience =
				maxDelta - ((maxDelta - minDelta) * i) / iterations;

			//find two different random points.
			randomX1 = (int) (Math.random() * size);
			randomZ1 = (int) (Math.random() * size);

			do {
				randomX2 = (int) (Math.random() * size);
				randomZ2 = (int) (Math.random() * size);
			} while (randomX1 == randomX2 && randomZ1 == randomZ2);

			//calculate the direction of the line the two points create.
			directionX1 = randomX2 - randomX1;
			directionZ1 = randomZ2 - randomZ1;

			for (int x = 0; x < size; x++) {
				for (int z = 0; z < size; z++) {
					//calculate the direction of the line from the first 
					//point to where we currently are.
					directionX2 = x - randomX1;
					directionZ2 = z - randomZ1;

					//If the direction between the two directions is positive,
					//the current point is above the line and should be 
					//increased by the height varient.
					if ((directionX2 * directionZ1 - directionX1 * directionZ2)
						> 0) {
						tempBuffer[x][z] += heightVarience;
					}
				}
			}
		}

		erodeTerrain(tempBuffer);
		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], j, i);
			}
		}

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

	/**
	 * <code>setIterations</code> sets the number of faults to generated during
	 * the construction of the heightmap.
	 * @param iterations the number of fault lines to generate.
	 * @throws MonkeyRuntimeException if the number of faults is less than or
	 *      equal to zero.
	 */
	public void setIterations(int iterations) {
		if (iterations <= 0) {
			throw new MonkeyRuntimeException(
				"iterations must be greater than " + "zero");
		}
		this.iterations = iterations;
	}

	/**
	 * <code>setMinDelta</code> sets the minimum height value for the 
	 * fault line varience. 
	 * @param minDelta the minimum height value for the fault line varience.
	 * @throws MonkeyRuntimeException if minDelta is greater than maxDelta.
	 */
	public void setMinDelta(int minDelta) {
		if (minDelta > maxDelta) {
			throw new MonkeyRuntimeException(
				"minDelta must be greater than " + "the current maxDelta");
		}
		this.minDelta = minDelta;
	}

	/**
	 * <code>setMaxDelta</code> sets the maximum height value for the fault
	 * line varience.
	 * @param maxDelta the maximum height value for the fault line varience.
	 * @throws MonkeyRuntimeException if maxDelta is less than minDelta.
	 */
	public void setMaxDelta(int maxDelta) {
		if (maxDelta < minDelta) {
			throw new MonkeyRuntimeException(
				"maxDelta must be greater than " + "current minDelta");
		}
		this.maxDelta = maxDelta;
	}

}