/******************************************************************************
 * mach64 Chapter 6 sample code                                               *
 *                                                                            *
 * line.c - This program uses the mach64 engine to draw lines.                *
 *                                                                            *
 * Copyright (c) 1994-1998 ATI Technologies Inc.  All rights reserved.        *
 ******************************************************************************/

#include <stdio.h>
#include <i86.h>
#include "..\util\atim64.h"
#include "..\util\defines.h"
#include "..\util\main.h"

// Prototypes.

void draw_a_line (int x1, int y1, int x2, int y2);
void draw_a_line24 (int x1, int y1, int x2, int y2);

/******************************************************************************
 * Main Program to demonstrate line drawing                                   *
 *  Function: Demonstrates mach64 engine line drawing feature.                *
 *    Inputs: Arguments for mode spatial and colour resolution                *
 *   Outputs: NONE                                                            *
 ******************************************************************************/

void main (int argc, char *argv[])
{
    int width;                          // Width of drawing area.
    int height;                         // Height of drawing area.
    int i;                              // Counter.
    int j = 0;                          // Colour counter.
    int m;                              // Number of vertical-ish lines.
    int n;                              // Number of horizonal-ish lines.
    int col;                            // Colour of line.
    int x1;                             // x coordinate of start of line.
    int y1;                             // y coordinate of start of line.
    int x2;                             // x coordinate of end of line.
    int y2;                             // y coordinate of end of line.

    printf ("mach64 Chapter 6 sample code\n"
            "\n"
            "line.c\n"
            "This program demonstrates the simple line draw function of the mach64\n"
            "engine.\n"
            "Spatial resolution (640, 800, 1024, 1280, 1600) and Colour Depth\n"
            "(8, 15, 16, 24, 32) should be passed as arguments.\n"
            "Default setting is 640x480 spatial resolution and 8bpp pixel depth.\n");

    // Batch command to detect the mach64, perform a hardware query, Save old
    // mode information, process mode info arguments, load and set mode, enable
    // aperture, set up palettes, initialize engine to known state, and reset
    // all engine queues.

    start (argc, argv);

    // Set up values.
    width = 640;                        // Less than or equal to xres.
    height = 480;                       // Less than or equal to yres.
    m = width/20;                       // Less than or equal to width.
    n = height/20;                      // Less than or equal to height.

    clear_screen (0, 0, MODE_INFO.xres, MODE_INFO.yres);

    // Draw vertical-ish lines.
    for (i = 0; i < width; i += (width/m))
    {
        // Setup parameters.

        col = (j%(NUM_COLOURS - 1)) + 1;
        x1 = ((MODE_INFO.xres - width)/2) + i - 1;
        y1 = ((MODE_INFO.yres - height)/2) - 1;
        x2 = ((MODE_INFO.xres + width)/2) - i - 1;
        y2 = ((MODE_INFO.yres + height)/2) - 1;

        // Draw line.

        wait_for_fifo (2);
        regw (DP_FRGD_CLR, get_colour_code (col));
        regw (DP_SRC, BKGD_SRC_BKGD_CLR | FRGD_SRC_FRGD_CLR | MONO_SRC_ONE);
        draw_a_line (x1, y1, x2, y2);
        j++;
    } // for

    // Draw horizontal-ish lines.
    for (i = 0; i < height; i += (height/n))
    {
        // Setup parameters.

        col = (j%(NUM_COLOURS - 1)) + 1;
        x1 = ((MODE_INFO.xres - width)/2) - 1;
        y1 = ((MODE_INFO.yres + height)/2) - i - 1;
        x2 = ((MODE_INFO.xres + width)/2) - 1;
        y2 = ((MODE_INFO.yres - height)/2) + i - 1;

        // Draw line.

        wait_for_fifo (2);
        regw (DP_FRGD_CLR, get_colour_code (col));
        regw (DP_SRC, BKGD_SRC_BKGD_CLR | FRGD_SRC_FRGD_CLR | MONO_SRC_ONE);
        draw_a_line (x1, y1, x2, y2);
        j++;
    } // for

    // Draw white border.
    wait_for_fifo (2);
    regw (DP_FRGD_CLR, get_colour_code (WHITE));
    regw (DP_SRC, BKGD_SRC_BKGD_CLR | FRGD_SRC_FRGD_CLR | MONO_SRC_ONE);
    draw_a_line ((MODE_INFO.xres - 1 - width)/2,
                 (MODE_INFO.yres - 1 - height)/2,
                 (MODE_INFO.xres - 1 + width)/2,
                 (MODE_INFO.yres-1-height)/2);
    draw_a_line ((MODE_INFO.xres - 1 + width)/2,
                 (MODE_INFO.yres - 1 - height)/2,
                 (MODE_INFO.xres - 1 + width)/2,
                 (MODE_INFO.yres - 1 + height)/2);
    draw_a_line ((MODE_INFO.xres - 1 + width)/2,
                 (MODE_INFO.yres - 1 + height)/2,
                 (MODE_INFO.xres - 1 - width)/2,
                 (MODE_INFO.yres - 1 + height)/2);
    draw_a_line ((MODE_INFO.xres - 1 - width)/2,
                 (MODE_INFO.yres - 1 + height)/2,
                 (MODE_INFO.xres - 1 - width)/2,
                 (MODE_INFO.yres - 1 - height)/2);

    // Wait for user to press a key.
    getch ();

    // Batch command to restore old mode.
    finish ();

    exit (0);                           // No errors.

} // main


/******************************************************************************
 * draw_a_line                                                                *
 *  Function: draws a line from (x1, y1) to (x2, y2) using the Bresenham      *
 *            algorithm and the mach64 engine.                                *
 *    Inputs: x1 - starting x coordinate in pixels                            *
 *            y1 - starting y coordinate in pixels                            *
 *            x2 - ending x coordinate in pixels                              *
 *            y2 - ending y coordinate in pixels                              *
 *   Outputs: NONE                                                            *
 *     Notes: The drawing of the last pixel in the line is determined         *
 *            by the current setting of the DST_CNTL register (LAST_PEL       *
 *            bit).                                                           *
 *            24 bpp uses a special routine.                                  *
 ******************************************************************************/

void draw_a_line (int x1, int y1, int x2, int y2)
{
    int dx, dy;
    int small, large;
    int x_dir, y_dir, y_major;
    unsigned long err, inc, dec, temp;

    // Call specific routine if mode is in 24 bpp.
    if (MODE_INFO.bpp == 24)
    {
        draw_a_line24 (x1, y1, x2, y2);
        return;
    } // if

    // Determine x & y deltas and x & y direction bits.
    if (x1 < x2)
    {
        dx = x2 - x1;
        x_dir = 1;
    }
    else
    {
        dx = x1 - x2;
        x_dir = 0;
    } // if

    if (y1 < y2)
    {
        dy = y2 - y1;
        y_dir = 2;
    }
    else
    {
        dy = y1 - y2;
        y_dir = 0;
    } // if

    // Determine x & y min and max values; also determine y major bit.
    if (dx < dy)
    {
        small = dx;
        large = dy;
        y_major = 4;
    }
    else
    {
        small = dy;
        large = dx;
        y_major = 0;
    } // if

    // Calculate Bresenham parameters and draw line.
    err = (unsigned long) ((2 * small) - large);
    inc = (unsigned long) (2 * small);
    dec = 0x3FFFF - ((unsigned long) (2 * (large - small)));

    // Wait for idle before reading GUI registers.
    wait_for_idle ();

    // Save used registers.
    temp = regr (DST_CNTL);

    // Draw Bresenham line.
    regw (DST_X, (unsigned long) x1);
    regw (DST_Y, (unsigned long) y1);

    // Allow setting of last pel bit and polygon outline bit for line drawing.
    regw (DST_CNTL, (temp & 0x60) | (unsigned long) (y_major | y_dir | x_dir));
    regw (DST_BRES_ERR, err);
    regw (DST_BRES_INC, inc);
    regw (DST_BRES_DEC, dec);
    regw (DST_BRES_LNTH, (unsigned long) (large + 1));

    // Restore.
    regw (DST_CNTL, temp);

    return;

} // draw_a_line


/******************************************************************************
 * draw_a_line24                                                              *
 *  Function: draws a line from (x1, y1) to (x2, y2) using the Bresenham      *
 *            algorithm and the mach64 engine for 24bpp modes.                *
 *    Inputs: x1 - starting x coordinate in pixels                            *
 *            y1 - starting y coordinate in pixels                            *
 *            x2 - ending x coordinate in pixels                              *
 *            y2 - ending y coordinate in pixels                              *
 *   Outputs: NONE                                                            *
 *     Notes: Since the engine does not directly support 24 bpp modes,        *
 *            it is set to 8 bpp while the CRTC is set to 24 bpp display      *
 *            mode (RGB). For rectangle drawing, all X coordinates and        *
 *            widths must be converted to 8 bpp sizes. This is done by        *
 *            taking the 24 bpp value and multipling it by 3.                 *
 ******************************************************************************/

void draw_a_line24 (int x1, int y1, int x2, int y2)
{
    int x, y, xend, yend, dx, dy;
    int d, incr1, incr2, incr3;
    unsigned long rotation, temp1, temp2;

    // Save register.
    wait_for_idle ();
    temp1 = regr (DST_CNTL);
    temp2 = 0xA3;

    // Bresenham line routine.
    dx = abs (x2 - x1);
    dy = abs (y2 - y1);

    // Check slope.
    if (dy <= dx)
    {
        // Slope <= 1.

        if (x1 > x2)
        {
            x = x2;
            y = y2;
            xend = x1;
            dy = y1 - y2;
        }
        else
        {
            x = x1;
            y = y1;
            xend = x2;
            dy = y2 - y1;
        } // if

        d = (2 * dy) - dx;
        incr1 = 2 * dy;
        incr2 = 2 * (dy - dx);
        incr3 = 2 * (dy + dx);

        regw (DST_HEIGHT, 1);
        regw (DST_Y, y);

        do
        {
            wait_for_fifo (4);
            rotation = (unsigned long) (((x*3)/4)%6);
            regw (DST_CNTL, temp2 | (rotation << 8));
            regw (DST_X, x * 3);
            regw (DST_WIDTH, 3);

            x++;

            if (d >= 0)
            {
                if (dy <= 0)
                {
                    d = d + incr1;
                }
                else
                {
                    y++;
                    regw (DST_Y, y);
                    d = d + incr2;
                } // if
            }
            else
            {
                if (dy >= 0)
                {
                    d = d + incr1;
                }
                else
                {
                    y--;
                    regw (DST_Y, y);
                    d = d + incr3;
                } // if
            } // if
        } while (x <= xend);
    }
    else
    {
        // Slope > 1.

        if (y1 > y2)
        {
            y = y2;
            x = x2;
            yend = y1;
            dx = x1 - x2;
        }
        else
        {
            y = y1;
            x = x1;
            yend = y2;
            dx = x2 - x1;
        } // if

        d = (2 * dx) - dy;
        incr1 = 2 * dx;
        incr2 = 2 * (dx - dy);
        incr3 = 2 * (dx + dy);

        regw (DST_HEIGHT, 1);

        do
        {
            wait_for_fifo (3);
            rotation = (unsigned long) (((x*3)/4)%6);
            regw (DST_CNTL, temp2 | (rotation << 8));
            regw (DST_Y_X, ((unsigned long) (x * 3) << 16) | y);
            regw (DST_WIDTH, 3);

            y++;

            if (d >= 0)
            {
                if (dx <= 0)
                {
                    d = d + incr1;
                }
                else
                {
                    x++;
                    d = d + incr2;
                } // if
            }
            else
            {
                if (dx >= 0)
                {
                    d = d + incr1;
                }
                else
                {
                    x--;
                    d = d + incr3;
                } // if
            } // if
        } while (y <= yend);
    } // if

    // Restore register.
    wait_for_fifo (1);
    regw (DST_CNTL, temp1);

    return;

} // draw_a_line24
