package edu.oregonstate.cartography.grid.operators.shader;

import edu.oregonstate.cartography.grid.Grid;

/**
 * A NormalVectorProvider that computes normals, gradient and orientation from a
 * grid.
 *
 * @author Bernhard Jenny, Faculty of Information Technology, Monash University,
 * Melbourne, Australia
 */
public class GridNormalVectors implements NormalVectorProvider {

    private final Grid grid;

    public GridNormalVectors(Grid grid) {
        this.grid = grid;
    }

    /**
     * Returns the dimensionless rise/run gradient computed from 8 neighboring
     * cells.
     *
     * @param col
     * @param row
     * @return
     */
    @Override
    public float gradient(int col, int row) {
        return grid.get8NeighborGradient(col, row);
    }

    /**
     * Returns the terrain orientation angle in radians. Terrain orientation is
     * the angle in the horizontal plane along the steepest slope. This is
     * similar to the aspect angle, but is counter-clockwise counted from east,
     * and between -pi and pi.
     *
     * @param col
     * @param row
     * @return orientation in radiant.
     */
    @Override
    public float orientationRad(int col, int row) {
        return (float)grid.getOrientation(col, row);
    }

    /**
     * Returns the normal vector at col/row. The normal vector is computed from
     * the four closest neighbors.
     *
     * @param col column of cell
     * @param row row fo cell
     * @return normal vector with length 1.
     */
    @Override
    public NormalVector normal(int col, int row) {
        if (col == 0 || row == 0 || col == grid.getCols() - 1 || row == grid.getRows() - 1) {
            return new NormalVector(0f, 0f, 1f);
        }

        // the cell size to calculate the horizontal components of vectors
        float cellSize = (float)grid.getProjectedCellSize(row);

        // z coordinate of normal vector
        float nz = 2 * cellSize;

        final float w = grid.getValue(col - 1, row);
        final float e = grid.getValue(col + 1, row);
        final float s = grid.getValue(col, row + 1);
        final float n = grid.getValue(col, row - 1);
        final float dx = e - w;
        final float dy = n - s;

        // normal vector on vertex
        float nx = -dx;
        float ny = -dy;
        final float nL = (float)Math.sqrt(dx * dx + dy * dy + nz * nz);
        nx /= nL;
        ny /= nL;
        nz /= nL;

        return new NormalVector(nx, ny, nz);
    }
}
