
/****************************************************************************
 *
 * MODULE:       display
 * AUTHOR(S):    CERL (original contributors)
 *               Bernhard Reiter <bernhard intevation.de>, 
 *               Markus Neteler <neteler itc.it>, 
 *               Glynn Clements <glynn gclements.plus.com>, 
 *               Hamish Bowman <hamish_b yahoo.com>
 * PURPOSE:      
 * COPYRIGHT:    (C) 1999-2007 by the GRASS Development Team
 *
 *               This program is free software under the GNU General Public
 *               License (>=v2). Read the file COPYING that comes with GRASS
 *               for details.
 *
 *****************************************************************************/

/*******************************************************************
 * Line drawing in the current window.
 *
 * Clip window:
 *   D_set_clip_window (top, bottom ,left, right)
 *      establish clipping region for subseqent line drawing.
 *   D_set_clip_window_to_map_window ()
 *     set clipping to pixels corresponding to the current map region
 *     (default)
 *   D_set_clip_window_to_screen_window ()
 *     set clipping to full extent of the window (ie disables clipping on screen)
 *
 * Moves.
 *   D_move_abs(x,y)   move to x,y.
 *   D_move_rel(x,y)   move to +x,+y.
 *      Set current position. Position is not clipped.
 *
 * Draw line 
 *   D_cont_abs(x,y)   draw to x,y.
 *   D_cont_rel(x,y)   draw to +x,+y.
 *      Line draw from current position. New postion is not clipped.
 *      The lines drawn are clipped however.
 *      Return values indicate the nature of the clipping:
 *        0 no clipping
 *        1 part of the line is drawn
 *       -1 none of the line is drawn
 *   
 *
 */
#include <grass/raster.h>
#include <grass/gis.h>
#include <grass/display.h>

static int clip(void);
static int line_eq(int, int, int, int, int, int);

static int curx, cury;
static int left, right, top, bottom;	/* window edges */
static int x1, y1, x2, y2;

static int window_set = 0;

#define swap(x,y) {int t; t=x; x=y; y=t;}
#define limit(a,x,b) x<a?a:(x>b?b:x)


/*!
 * \brief set clipping window
 *
 * Sets the clipping window to the pixel window that corresponds
 * to the current database region. This is the default.
 *
 *  \param top
 *  \param bottom
 *  \param left
 *  \param right
 *  \return int
 */

int D_set_clip_window(int Top, int Bottom, int Left, int Right)
{
    /* make sure top is above bottom, left is left of right */
    if (Top > Bottom)
	swap(Top, Bottom);
    if (Left > Right)
	swap(Left, Right);

    /* make sure edges are within the true window edges */
    D_get_screen_window(&top, &bottom, &left, &right);
    Top = limit(top, Top, bottom);
    Bottom = limit(top, Bottom, bottom);
    Left = limit(left, Left, right);
    Right = limit(left, Right, right);

    /* set the window */
    top = Top;
    bottom = Bottom;
    left = Left;
    right = Right;

    window_set = 1;

    R_move_abs(left, top);

    return 0;
}


/*!
 * \brief set clipping window to map window
 *
 * Sets the clipping window to the pixel window that corresponds to the
 * current database region. This is the default.
 *
 *  \param ~
 *  \return int
 */

int D_set_clip_window_to_map_window(void)
{
    D_set_clip_window((int)D_get_d_north(),
		      (int)D_get_d_south(),
		      (int)D_get_d_west(), (int)D_get_d_east()
	);

    return 0;
}


/*!
 * \brief set clipping window to screen window
 *
 * Sets the clipping window to the pixel window that corresponds to the
 * full screen window. Off screen rendering is still clipped.
 *
 *  \param ~
 *  \return int
 */

int D_set_clip_window_to_screen_window(void)
{
    D_get_screen_window(&top, &bottom, &left, &right);
    D_set_clip_window(top, bottom, left, right);

    return 0;
}


/*!
 * \brief line to x,y
 *
 * Draws a line from the
 * current position to pixel location <b>x,y.</b> Any part of the line that
 * falls outside the clipping window is not drawn.
 * <b>Note.</b> The new position is <b>x,y</b>, even if it falls outside the
 * clipping window. Returns 0 if the line was contained entirely in the clipping
 * window, 1 if the line had to be clipped to draw it.
 *
 *  \param x
 *  \param y
 *  \return int
 */

int D_cont_abs(int x, int y)
{
    int clipped;

    x1 = curx;
    y1 = cury;
    x2 = x;
    y2 = y;
    curx = x;
    cury = y;

    if (!window_set)
	D_set_clip_window_to_map_window();

    clipped = clip();
    if (clipped >= 0) {
	R_move_abs(x1, y1);
	R_cont_abs(x2, y2);
    }

    return clipped;
}


/*!
 * \brief line to x,y
 *
 * Equivalent to
 * <i>D_cont_abs</i>(curx+x, cury+y) where <b>curx, cury</b> is the current
 * pixel location.
 *
 *  \param x
 *  \param y
 *  \return int
 */

int D_cont_rel(int x, int y)
{
    return D_cont_abs(curx + x, cury + y);
}


/*!
 * \brief move to pixel
 *
 * Move without drawing to
 * pixel location <b>x,y</b>, even if it falls outside the clipping window.
 *
 *  \param x
 *  \param y
 *  \return int
 */

int D_move_abs(int x, int y)
{
    curx = x;
    cury = y;
    return 0;
}


/*!
 * \brief move to pixel
 *
 * Equivalent to
 * <i>D_move_abs</i>(curx+x, cury+y) where <b>curx, cury</b> is the current
 * pixel location.
 *
 *  \param x
 *  \param y
 *  \return int
 */

int D_move_rel(int x, int y)
{
    curx += x;
    cury += y;
    return 0;
}

/*********************************************************************
 * this code is the window clipping for D_cont_abs()
 *********************************************************************/

#define Y(x)  line_eq(x,x0,y0,dx,dy,xround)

#define X(y)  line_eq(y,y0,x0,dy,dx,yround)


static int clip(void)
{
    register int x0, y0;
    register int dx, dy;
    int xround;
    int yround;
    int clipped;

    /*
     * quick check for line above,below,left,or right of window
     */
    if (x1 < left && x2 < left)
	return -1;
    if (x1 > right && x2 > right)
	return -1;

    if (y1 < top && y2 < top)
	return -1;
    if (y1 > bottom && y2 > bottom)
	return -1;

    /*
     * setup line equations variables
     */
    x0 = x1;
    y0 = y1;

    dx = x2 - x1;
    dy = y2 - y1;

    if ((xround = dx / 2) < 0)
	xround = -xround;
    if ((yround = dy / 2) < 0)
	yround = -yround;

    /*
     * clipping
     *
     * if x of endpoint 1 doesn't fall within the window
     *    move x to the nearest edge
     *    recalculate the y
     *      if the new y doesn't fall within the window then
     *      the line doesn't cross into the window
     *
     * if y of endpoint 1 doesn't fall within the window
     *    move y to the nearest edge
     *    recalculate the x
     *      if the new x doesn't fall within the window then
     *      the line doesn't cross into the window
     *
     * repeat for the second endpoint
     *
     */

    clipped = 0;
    if (x1 < left || x1 > right) {
	if (dx == 0)
	    return -1;

	x1 = x1 < left ? left : right;
	y1 = Y(x1);

	if (y1 < top || y1 > bottom) {
	    if (dy == 0)
		return -1;

	    y1 = y1 < top ? top : bottom;
	    x1 = X(y1);

	    if (x1 < left || x1 > right)
		return -1;
	}
	clipped = 1;
    }
    if (y1 < top || y1 > bottom) {
	if (dy == 0)
	    return -1;
	y1 = y1 < top ? top : bottom;
	x1 = X(y1);

	if (x1 < left || x1 > right) {
	    if (dx == 0)
		return -1;

	    x1 = x1 < left ? left : right;
	    y1 = Y(x1);

	    if (y1 < top || y1 > bottom)
		return -1;
	}
	clipped = 1;
    }

    if (x2 < left || x2 > right) {
	if (dx == 0)
	    return -1;

	x2 = x2 < left ? left : right;
	y2 = Y(x2);

	if (y2 < top || y2 > bottom) {
	    if (dy == 0)
		return -1;

	    y2 = y2 < top ? top : bottom;
	    x2 = X(y2);

	    if (x2 < left || x2 > right)
		return -1;
	}
	clipped = 1;
    }
    if (y2 < top || y2 > bottom) {
	if (dy == 0)
	    return -1;

	y2 = y2 < top ? top : bottom;
	x2 = X(y2);

	if (x2 < left || x2 > right) {
	    if (dx == 0)
		return -1;

	    x2 = x2 < left ? left : right;
	    y2 = Y(x2);

	    if (y2 < top || y2 > bottom)
		return -1;
	}
	clipped = 1;
    }

    return clipped;
}

static int line_eq(int x, int x0, int y0, int dx, int dy, int round)
{
    register int t;

    if ((t = dy * (x - x0)) < 0)
	t -= round;
    else
	t += round;;

    return (y0 + t / dx);
}
