/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*	@(#)picasso:arcgen.c	1.0	*/
#include	"picasso.h"
#include	"y.tab.h"

obj *arcgen(type)	/* handles circular and (eventually) elliptical arcs */
{
	static	double	prevwid	= HT10;
	static	double	prevht	= HT5;
	static	double	prevrad	= HT2;
	static	int	dtox[2][4]   ={ 1, -1, -1, 1, 1, 1, -1, -1 };
	static	int	dtoy[2][4]   ={ 1, 1, -1, -1, -1, 1, 1, -1 };
	static	int	dctrx[2][4]  ={ 0, -1, 0, 1, 0, 1, 0, -1 };
	static	int	dctry[2][4]  ={ 1, 0, -1, 0, -1, 0, 1, 0 };
	static	int	nexthv[2][4] ={ U_DIR, L_DIR, D_DIR, R_DIR, D_DIR,
					R_DIR, U_DIR, L_DIR };
struct	objattr	obat;
	double	dx2, dy2, phi, r, d, fromx, fromy, tox, toy;
	int	i, head, to, at, cw;
	obj	*p, *ppos;
	Attr	*ap;

	obat.a_ht  = getfval("arrowht");
	obat.a_wid = getfval("arrowwid");
	obat.a_rad = getfval("arcrad");
	obat.a_layer = (int)getfval("curlayer");
	obat.a_flags = EDGED;
	obat.a_weight = obat.a_lcolor = obat.a_pcolor = obat.a_tcolor = -1;
	obat.a_dashpat.a = (float *)0;
	set_text();
	fromx = curx;
	fromy = cury;
	head = to = at = cw = 0;
	for (i = 0; i < nattr; i++) {
		ap = &attr[i];
		switch (ap->a_type) {
		default:
			miscattrs(ap, &obat);
			break;
		case HEAD:
			head += ap->a_val.i;
			break;
		case CW:
			cw = 1;
			break;
		case FROM:	/* start point of arc */
			ppos = ap->a_val.o;
			fromx = Xformx(ppos, 1, ppos->o_x, ppos->o_y);
			fromy = Xformy(ppos, 0, ppos->o_x, ppos->o_y);
			break;
		case TO:	/* end point of arc */
			ppos = ap->a_val.o;
			tox = Xformx(ppos, 1, ppos->o_x, ppos->o_y);
			toy = Xformy(ppos, 0, ppos->o_x, ppos->o_y);
			to++;
			break;
		case AT:	/* center of arc */
			ppos = ap->a_val.o;
			curx = Xformx(ppos, 1, ppos->o_x, ppos->o_y);
			cury = Xformy(ppos, 0, ppos->o_x, ppos->o_y);
			at = 1;
			break;
		case UP:
			hvmode = U_DIR;
			break;
		case DOWN:
			hvmode = D_DIR;
			break;
		case RIGHT:
			hvmode = R_DIR;
			break;
		case LEFT:
			hvmode = L_DIR;
			break;
		case SAME:
			obat.a_ht  = prevht;
			obat.a_wid = prevwid;
			obat.a_rad = prevrad;
			break;
		}
	}
	if (!at && !to) {	/* the defaults are mostly OK */
		curx = fromx + obat.a_rad * dctrx[cw][hvmode];
		cury = fromy + obat.a_rad * dctry[cw][hvmode];
		tox  = fromx + obat.a_rad * dtox[cw][hvmode];
		toy  = fromy + obat.a_rad * dtoy[cw][hvmode];
		hvmode = nexthv[cw][hvmode];
	}
	else if (to) {		/* then compute center */
		dx2 = (tox - fromx) / 2;
		dy2 = (toy - fromy) / 2;
		phi = atan2(dy2, dx2) + (cw ? -M_PI_2 : M_PI_2);
		/*  If from, to, at (and possibly radius) are all supplied,
		 *  the arc is overdetermined.  Recompute the radius (as
		 *  well as at (i.e., curx and cury)) in that case.
		 */
		if (at) {
			double  alpha;

			obat.a_rad = sqrt((curx - fromx) * (curx - fromx) +
					(cury - fromy) * (cury - fromy));
			alpha = atan2(toy - cury, tox - curx) -
					atan2(fromy - cury, fromx - curx);
			if (alpha < 0.)	alpha += 2 * M_PI;
			if (!cw && alpha > M_PI || cw && alpha < M_PI)
				phi += M_PI;
		}
		else if (obat.a_rad <= 0.0)
			obat.a_rad = sqrt(dx2*dx2+dy2*dy2);
		for (r=obat.a_rad; (d = r*r - (dx2*dx2+dy2*dy2)) <= 0.0; r *= 2)
			;	/* this kludge gets around too-small radii */
		obat.a_rad = r;
		d = sqrt(d);
		curx = fromx + dx2 + d * cos(phi);
		cury = fromy + dy2 + d * sin(phi);
	}
	else if (at && !to) {	/* do we have all the cases??? */
		tox = fromx + obat.a_rad * dtox[cw][hvmode];
		toy = fromy + obat.a_rad * dtoy[cw][hvmode];
		hvmode = nexthv[cw][hvmode];
	}
	p = makenode(type, N_VAL + 10, obat.a_layer);
	prevrad = p->o_val[N_VAL+0].f = p->o_val[N_VAL+1].f = obat.a_rad;
	prevwid = p->o_val[N_VAL+8].f = obat.a_wid;
	prevht  = p->o_val[N_VAL+9].f = obat.a_ht;
	if (cw) {	/* interchange roles of from-to and heads */
		double temp;
		temp = fromx; curx = fromx = tox; tox = temp;
		temp = fromy; cury = fromy = toy; toy = temp;
		if (head == HEAD1)
			head = HEAD2;
		else if (head == HEAD2)
			head = HEAD1;
		p->o_attr |= CW_ARC;
	}
	else {
		curx = tox;
		cury = toy;
	}
	p->o_val[N_VAL+2].f = fromx;
	p->o_val[N_VAL+3].f = fromy;
	p->o_val[N_VAL+4].f = tox;
	p->o_val[N_VAL+5].f = toy;
	if (head)
		p->o_attr |= head | arrowfill();
	primattrs(p, &obat);
	text_bounds(p);
	arc_extreme(p);
	return(p);
}

quadrant(x,y)
	double x, y;
{
	if (     x>=0.0 && y> 0.0) return(1);
	else if( x< 0.0 && y>=0.0) return(2);
	else if( x<=0.0 && y< 0.0) return(3);
	else if( x> 0.0 && y<=0.0) return(4);
	else
		{ fprintf(stderr,"can't happen: x,y=%g,%g",x,y); exit(1);}
}

/***************************************************************************
   bounding box of a circular arc             Eric Grosse  24 May 84

Conceptually, this routine generates a list consisting of the start,
end, and whichever north, east, south, and west points lie on the arc.
The bounding box is then the range of this list.
    list = {start,end}
    j = quadrant(start)
    k = quadrant(end)
    if( j==k && long way 'round )  append north,west,south,east
    else
      while( j != k )
         append center+radius*[j-th of north,west,south,east unit vectors]
         j += 1  (mod 4)
    set bounds (return object with wid/ht and offset from arc center)
The following code implements this, with simple optimizations.
***********************************************************************/

arc_extreme(p)
	obj	*p;
{
	/* assumes center isn't too far out */

	double	x0, y0, x1, y1, xc, yc;  /* start, end, center */
	double	r, xmin, ymin, xmax, ymax, wgt;
	int	j, k;

	x0 = p->o_val[N_VAL+2].f - (xc = p->o_x);    /* translate to center */
	y0 = p->o_val[N_VAL+3].f - (yc = p->o_y);
	x1 = p->o_val[N_VAL+4].f - xc;
	y1 = p->o_val[N_VAL+5].f - yc;
	xmin = (x0<x1)?x0:x1; ymin = (y0<y1)?y0:y1;
	xmax = (x0>x1)?x0:x1; ymax = (y0>y1)?y0:y1;
	r = p->o_val[N_VAL].f;
	if (r > 0.0) {
		j = quadrant(x0,y0);
		k = quadrant(x1,y1);
		if (j == k && y1*x0 <= x1*y0) {
		/* viewed as complex numbers, if Im(z1/z0)<0, arc is big */
			if( xmin > -r) xmin = -r; if( ymin > -r) ymin = -r;
			if( xmax <  r) xmax =  r; if( ymax <  r) ymax =  r;
		} else {
			while (j != k) {
				switch (j) {
					case 1: if( ymax <  r) ymax =  r; break; /* north */
					case 2: if( xmin > -r) xmin = -r; break; /* west */
					case 3: if( ymin > -r) ymin = -r; break; /* south */
					case 4: if( xmax <  r) xmax =  r; break; /* east */
				}
				j = j%4 + 1;
			}
		}
	}
	if (p->o_type == SECTOR) {	/* include center */
		if (xmin * xmax > 0)
			if (xmin > 0) xmin = 0; else xmax = 0;
		if (ymin * ymax > 0)
			if (ymin > 0) ymin = 0; else ymax = 0;
	}
	p->o_wid = xmax - xmin;
	p->o_ht  = ymax - ymin;
	p->o_val[N_VAL+6].f = -0.5 * (xmin+xmax);  /* record offset to center */
	p->o_val[N_VAL+7].f = -0.5 * (ymin+ymax);
	wgt = p->o_weight/2;
	track_bounds(xmin+xc-wgt, ymin+yc-wgt, xmax+xc+wgt, ymax+yc+wgt);
}
