/*	beztwist.c	17-Sep-88
**
**	Display some twisting cubic Bezier curves.
**	Shows speed of code generated by the Turbo C compiler.
**	Works with all 3 resolution modes of the Atari ST.
*/

#include <tos.h>
#include <stdlib.h>
#include "grtypes.h"

#define NULL	((void *) 0L)
#define NIL	((void *) -1L)


typedef struct {
    SCREEN	screen;
    RESOLUTION	res;
    int		planes, words_per_line;
    COLOR	color, col_mask;
    COORD	x_max, y_max;
    long	max_area;
} INFO;

typedef struct {
    COORD	x[4];
    COORD	y[4];
    int		active;
} BEZIER;

#define	N	10	/* length of FIFO queue */
#define	FLAT	32
#define CONSOLE	2

#define	BITS		5
#define SCALE		(1 << BITS)
#define SCALE_2		(1 << (BITS-1))



/*	draw_line()
**
**	Draw a line from point (x_from, y_from) to point (x_to, y_to).
**	Use Bresenham's line drawing algorithm with incremental
**	screen address computation and inline plotting. (Fast!)
*/

void
draw_line(	register INFO *info,
		COORD x_from, COORD y_from,
		COORD x_to, COORD y_to)
{
    register SCREEN screen;
    COORD delta_x, delta_y, incr_left, incr_left_up;
    COORD count, dist, offset;
    register COLOR color;
    register WORD or_mask, and_mask;
    int flag;

    delta_y = y_to - y_from;
    if ((delta_x = x_to - x_from) < 0) {
	delta_x = -delta_x;
	delta_y = -delta_y;
	x_from = x_to;
	y_from = y_to;
    }
    if (delta_y < 0) {
	offset = -info->words_per_line;
	delta_y = -delta_y;
    } else {
	offset = info->words_per_line;
    }
    if (delta_x < delta_y) {
	flag = 1;
	count = delta_y;
	delta_y = delta_x;
	delta_x = count;
    } else {
	flag = 0;
	count = delta_x;
    }

    screen =	info->screen +
		((y_from * info->words_per_line) +
		 (x_from >> 4) * info->planes);
    or_mask = 0x8000 >> (x_from & 0x0f);
    incr_left = 2 * delta_y;
    dist = incr_left - delta_x;
    incr_left_up = dist - delta_x;
    color = info->color;

    for (;;) {
	and_mask = ~or_mask;
	switch (info->res) {
	case low_res:
	    if (color & 8)	screen[3] |= or_mask;
	    else		screen[3] &= and_mask;
	    if (color & 4)	screen[2] |= or_mask;
	    else		screen[2] &= and_mask;
	case med_res:
	    if (color & 2)    	screen[1] |= or_mask;
	    else	    	screen[1] &= and_mask;
	case hi_res:
	    if (color & 1)	screen[0] |= or_mask;
	    else		screen[0] &= and_mask;
	default:
	    break;
	}
	
	if (count == 0) break;
	count--;
	
	if (dist >= 0) {
	    /* Move 1 pixel 'left' and 'up'. */
	    dist += incr_left_up;
	    or_mask >>= 1;
	    screen += offset;
	} else {
	    /* Move 1 pixel 'left'. */
	    dist += incr_left;
	    if (flag)
		screen += offset;
	    else
		or_mask >>= 1;
	}
	
	if (or_mask == 0x0000) {
	    screen += info->planes;
	    or_mask = 0x8000;
	}
    }
}


/*	bezier()
**
**	Draw the cubic Bezier curve specified by P0, P1, P2 and P3
**	by subdividing it recursively into sub-curves until it
**	can be approximated smoothly by straight line segments.
**	We use fixed point arithmetic to achieve increased precision
**	in coordinate computations. The control points have been
**	scaled with SCALE and 0.5 has been added to the coordinates
**	so that we can feed rounded coordinate values to draw_line().
*/

void
bezier(	INFO *info,
	long x0, long y0, long x1, long y1,
	long x2, long y2, long x3, long y3)
{
    long v1x, v1y, v2x, v2y, v3x, v3y;
    long vcp1, vcp2, vcp3, area;
    long mid_x, mid_y;
    int code;

    /*
    ** Stop the recursion when the size of the area covered
    ** by the convex hull of the Bezier curve is less than or
    ** equal to info->max_area.
    */

    /* First, compute direction vectors. */
    v3x = x3 - x0;    v3y = y3 - y0;
    v2x = x2 - x0;    v2y = y2 - y0;
    v1x = x1 - x0;    v1y = y1 - y0;

    /* Then, compute vector cross products. */
    code = 0;
    if ((vcp1 = v3x * v2y - v2x * v3y) < 0) code += 4;
    if ((vcp2 = v3x * v1y - v1x * v3y) < 0) code += 2;
    if ((vcp3 = v2x * v1y - v1x * v2y) < 0) code += 1;

    /* Finally, compute size of area covered by convex hull.	  */
    /* We actually compute 2*area, but that doesn't matter much.  */
    switch (code) {
    case 0:
    case 2:
    case 5:
    case 7:	area = vcp1 + vcp3;	break;
    case 1:
    case 6:	area = vcp2 - vcp3;	break;
    case 3:
    case 4:	area = vcp1 - vcp2;	break;
    default:	return;
    }
    if (code & 4) area = -area;

    if (area <= info->max_area) {
	/* Stop recursion and draw a line from P0 to P3. */
	
	/* Rescale and round coordinates before    */
	/* feeding them to draw_line().		   */
	draw_line(info,
		x0 >> BITS, y0 >> BITS, x3 >> BITS, y3 >> BITS);

    } else {
	/* Area is still too big, so subdivide the curve into	*/
	/* two sub-curves and draw these recursively.		*/

	mid_x = (x0 + 3*x1 + 3*x2 + x3) >> 3;
	mid_y = (y0 + 3*y1 + 3*y2 + y3) >> 3;
	bezier(	info,
		x0, y0,
		(x0 + x1) >> 1, (y0 + y1) >> 1,
		(x0 + 2*x1 + x2) >> 2, (y0 + 2*y1 + y2) >> 2,
		mid_x, mid_y);
	bezier(	info,
		mid_x, mid_y,
		(x1 + 2*x2 + x3) >> 2, (y1 + 2*y2 + y3) >> 2,
		(x2 + x3) >> 1, (y2 + y3) >> 1,
		x3, y3);
    }
}


/*
**	Show some randomly twisting bezier curves.
*/

int
main(argc, argv)
int argc;
char *argv[];
{
    INFO	info;
    BEZIER	list[N];
    BEZIER	*q;
    COORD	dx[4], dy[4];
    COORD	*p;
    COLOR	color;
    int		i, j, k, n;
    long	loop = -1, flat = 0;
    
    if (argc >= 2)
	loop = atoi(argv[1]);
    if (loop < 1)
	loop = -1;
    if (argc >= 3)
	flat = atoi(argv[2]);

    info.screen = Physbase();
    Setscreen(NIL, NIL, info.res = Getrez());

    switch (info.res) {
    case hi_res:
	info.x_max = HI_X_MAX;
	info.y_max = HI_Y_MAX;
	info.planes = HI_PLANES;
	info.words_per_line = HI_WORDS_PER_LINE;
	info.max_area = (flat >= 1 ? flat : FLAT) * SCALE * SCALE;
	info.col_mask = HI_COLORS - 1;
	color = HI_COLORS - 1;
	break;
    case med_res:
	info.x_max = MED_X_MAX;
	info.y_max = MED_Y_MAX;
	info.planes = MED_PLANES;
	info.words_per_line = MED_WORDS_PER_LINE;
	info.max_area = (flat >= 1 ? flat : (3*FLAT)/4) * SCALE * SCALE;
	info.col_mask = MED_COLORS - 1;
	color =  MED_COLORS - 1;
	break;
    case low_res:
	info.x_max = LOW_X_MAX;
	info.y_max = LOW_Y_MAX;
	info.planes = LOW_PLANES;
	info.words_per_line = LOW_WORDS_PER_LINE;
	info.max_area = (flat >= 1 ? flat : FLAT/2) * SCALE * SCALE;
	info.col_mask = LOW_COLORS - 1;
	color = LOW_COLORS - 1;
	break;
    default:
	return -1;
    }

    for (i = 1; i < N; i++)
	list[i].active = 0;

    i = 0;
    q = &(list[0]);
    q->active = 1;
    for (k = 0; k < 4; k++) {
	q->x[k] = Random() % (info.x_max + 1);
	q->y[k] = Random() % (info.y_max + 1);

	n = (Random() & 15) - 7;
	if (n == 0) n = 1;
	else if (n == 8) n = -1;
	dx[k] = n;
	
	n = (Random() & 15) - 7;
	if (n == 0) n = 1;
	else if (n == 8) n = -1;
	dy[k] = n;
    }

    while (loop--) {
	info.color = color;
	bezier(	&info,
		q->x[0] * SCALE + SCALE_2, q->y[0] * SCALE + SCALE_2,
		q->x[1] * SCALE + SCALE_2, q->y[1] * SCALE + SCALE_2,
		q->x[2] * SCALE + SCALE_2, q->y[2] * SCALE + SCALE_2,
		q->x[3] * SCALE + SCALE_2, q->y[3] * SCALE + SCALE_2);

	j = i;
	i = (i + 1) % N;
	q = &(list[i]);

    	if (q->active) {
	    info.color = 0;
	    bezier(	&info,
		q->x[0] * SCALE + SCALE_2, q->y[0] * SCALE + SCALE_2,
		q->x[1] * SCALE + SCALE_2, q->y[1] * SCALE + SCALE_2,
		q->x[2] * SCALE + SCALE_2, q->y[2] * SCALE + SCALE_2,
		q->x[3] * SCALE + SCALE_2, q->y[3] * SCALE + SCALE_2);
	}

	q->active = 1;

	for (k = 0; k < 4; k++) {
	    p = &(q->x[k]);
	    *p = list[j].x[k] + dx[k];
	    if (*p < 0) {
		*p += 8;
		dx[k] = -dx[k];
	    } else if (*p > info.x_max) {
		*p -= 8;
		dx[k] = -dx[k];
	    }

	    p = &(q->y[k]);
	    *p = list[j].y[k] + dy[k];
	    if (*p < 0) {
		*p += 8;
		dy[k] = -dy[k];
	    } else if (*p > info.y_max) {
		*p -= 8;
		dy[k] = -dy[k];
	    }
	}

	if ((loop & 0xff) == 0) {
	    /* change direction */
	    k = Random() & 3;
	    n = (Random() & 15) - 7;
	    if (n == 0) n = 1;
	    else if (n == 8) n = -1;
	    if (Random() & 1)
		dx[k] = n;
	    else
		dy[k] = n;

	    /* change color */
	    color = (Random() % info.col_mask) + 1;
	}

	/* exit if any key pressed */
	if (Bconstat(CONSOLE)) loop = 0;
    }

    return 0;
}

