/*	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:xform.c	1.0	*/

#include	"picasso.h"
#include	"y.tab.h"
#include    <math.h>

float	TINYNUM = 10e-4;

/* shorthand for a 3x3 affine matrix applied as follows:
 *
 *	C(x, y)	= |x y 1| | C[0]  C[1]  0 | 
 *		 	  | C[2]  C[3]  0 |
 *		 	  | C[4]  C[5]  1 |
 *
 *		= (C[0]x+C[2]y+C[4], C[1]x+C[3]y+C[5])
 */

matmult (A, B)			/* A <- A * B; A is an object transformation */
	valtype	A[6];		/* or cur_xform updated by a new transform.  */
	double	B[6];
{
	register double	p;

	p = A[0].f * B[0] + A[1].f * B[2];
	A[1].f = A[0].f * B[1] + A[1].f * B[3];
	A[0].f = p;

	p = A[2].f * B[0] + A[3].f * B[2];
	A[3].f = A[2].f * B[1] + A[3].f * B[3];
	A[2].f = p;

	p = A[4].f * B[0] + A[5].f * B[2] + B[4];
	A[5].f = A[4].f * B[1] + A[5].f * B[3] + B[5];
	A[4].f = p;
	identity_coercion(A);
}

identity_coercion(A)
	valtype	A[6];
{
	/*
	 *	Coerce to identity matrix if sufficiently close to one.
	 */
	if (A[0].f != 1.0 && fabs(A[0].f - 1.0) < TINYNUM)	A[0].f = 1.0;
	if (A[3].f != 1.0 && fabs(A[3].f - 1.0) < TINYNUM)	A[3].f = 1.0;
	if (A[1].f != 0.0 && fabs(A[1].f) < TINYNUM)	A[1].f = 0.0;
	if (A[2].f != 0.0 && fabs(A[2].f) < TINYNUM)	A[2].f = 0.0;
	if (A[4].f != 0.0 && fabs(A[4].f) < TINYNUM)	A[4].f = 0.0;
	if (A[5].f != 0.0 && fabs(A[5].f) < TINYNUM)	A[5].f = 0.0;
}

matmult2 (A, B)			/* same, but updates a double array; this is  */
	double	A[6];		/* done only in going up the block hierarchy. */
	valtype	B[6];
{
	register double	p;

	p = A[0] * B[0].f + A[1] * B[2].f;
	A[1] = A[0] * B[1].f + A[1] * B[3].f;
	A[0] = p;

	p = A[2] * B[0].f + A[3] * B[2].f;
	A[3] = A[2] * B[1].f + A[3] * B[3].f;
	A[2] = p;

	p = A[4] * B[0].f + A[5] * B[2].f + B[4].f;
	A[5] = A[4] * B[1].f + A[5] * B[3].f + B[5].f;
	A[4] = p;
}

void
mat_inverse(double Ainv[6], float A[6])
{
	double	det, det_1;

	det = A[0] * A[3] - A[1] * A[2];
	if (det == 0.0) {
		Ainv[0] = Ainv[3] = 1.0;
		Ainv[1] = Ainv[2] = 0.0;
		Ainv[4] = Ainv[5] = 0.0;
		return;
	}
	det_1 = 1.0 / det;
	Ainv[0] = A[3] * det_1;
	Ainv[1] = - A[1] * det_1;
	Ainv[2] = - A[2] * det_1;
	Ainv[3] = A[0] * det_1;
	Ainv[4] = (A[2] * A[5] - A[3] * A[4]) * det_1;
	Ainv[5] = (A[1] * A[4] - A[0] * A[5]) * det_1;
}

double	T[6];

void
compose (obj *p)		/* accumulate xforms over block hierarchy */
{
	if (p == NULL || p->o_type > TEXT) {
		T[0] = T[3] = 1;
		T[1] = T[2] =
		T[4] = T[5] = 0;
		if (p == NULL)
			return;
	}
	else {
		T[0] = p->o_mxx;	T[1] = p->o_myx;
		T[2] = p->o_mxy;	T[3] = p->o_myy;
		T[4] = p->o_mxt;	T[5] = p->o_myt;
	}
	while ((p = p->o_parent) != NULL)
		if (p->o_type <= TEXT)
			if (p->o_mxx != 1 || p->o_mxy != 0 || p->o_mxt != 0
			||  p->o_myx != 0 || p->o_myy != 1 || p->o_myt != 0)
				matmult2(T, p->o_xform);
}

get_matrix(M0, M1, M2, M3)
	double	*M0, *M1, *M2, *M3;
{
	*M0 = T[0];	*M1 = T[1];	*M2 = T[2];	*M3 = T[3];
}

double	Xformx (p, flag, x, y)
	obj	*p;
	int	flag;
	double	x, y;
{
	if (flag)
		compose(p);
	return x*T[0] + y*T[2] + T[4];
}

double	Xformy (p, flag, x, y)
	obj	*p;
	int	flag;
	double	x, y;
{
	if (flag)
		compose(p);
	return x*T[1] + y*T[3] + T[5];
}

double	Linx (p, flag, x, y)		/* linear component, only */
	obj	*p;
	int	flag;
	double	x, y;
{
	if (flag)
		compose(p);
	return T[0]*x + T[2]*y;
}

double	Liny (p, flag, x, y)
	obj	*p;
	int	flag;
	double	x, y;
{
	if (flag)
		compose(p);
	return T[1]*x + T[3]*y;
}

apply (p, M, d)			/* apply matrix M (determinant d) to object p */
	obj	*p;
	double  *M, d;
{
	float   bnd[4];

	if (p->o_type == PLACE) {
		matmult (cur_xform, M);
		if (d != 1)
			checkscale(sqrt(d));
	}
	else if (p->o_type <= TEXT) {
		matmult (p->o_xform, M);
		get_bounds(p, bnd, 0);
		track_bounds(bnd[0], bnd[1], bnd[2], bnd[3]);
		redo_gbox = 1;
	}
}

translate (p, x, y)           /* the easy case of applying a transformation */
	obj     *p;
	double	x, y;
{
	float	bnd[4];
	int	i;

	if (p->o_type == PLACE) {
		cur_xform[4].f += x;
		cur_xform[5].f += y;
	}
	else if (p->o_type <= TEXT) {
		if (p->o_type == BLOCK  || p->o_mxx != 1 || p->o_myy != 1
					|| p->o_mxy != 0 || p->o_myx != 0
					|| p->o_mxt != 0 || p->o_myt != 0) {
			p->o_mxt += x;
			p->o_myt += y;
		}
		else {	/* no existing transformation, move center */
			/* DBK: probably should also test to make sure
			   there is no higher level transformation?? */
			p->o_x += x;
			p->o_y += y;
			switch (p->o_type) {

			case LINE:
			case ARROW:
			case SPLINE:
				for (i = 0; i <= p->o_val[N_VAL+3].f; i++) {
					p->o_val[N_VAL+4+i+i].f += x;
					p->o_val[N_VAL+5+i+i].f += y;
				}
				break;
			case ARC:
			case SECTOR:
				p->o_val[N_VAL+2].f += x;
				p->o_val[N_VAL+3].f += y;
				p->o_val[N_VAL+4].f += x;
				p->o_val[N_VAL+5].f += y;
				break;
			}
		}
		get_bounds(p, bnd, 0);
		track_bounds(bnd[0], bnd[1], bnd[2], bnd[3]);
		redo_gbox = 1;
	}
}

xlate (p, q)
	obj	*p, *q;
{
	translate (p, q->o_x, q->o_y);
}

xlate_to (p, q)			/* destination given in absolute coordinates */
	obj	*p, *q;
{
	translate (p, q->o_x - p->o_x, q->o_y - p->o_y);
}

static double	picscale = 1.0;

rescale (v)		/* implicit scaling from user's "scale = v" statement */
	double	v;	/* rather unintuitively, this is like "scale O 1/v".  */
{
	picscale = v;
}

void
scale_pic(void)		/* for PIC compatibility--called at end of parse */
{
	double	sc;
	obj	*o;
	int	i;
	float	bnd[4];

	if (picscale == 0.0)
		picscale = 1.0;
	if (picscale == 1.0)
		return;
	sc = 1.0 / picscale;
	
	for (o = objhead; o != objtail; o = o->o_next) {
		if (o->o_type == PLACE || o->o_type == TEXT ||
						o->o_type == MOVE) {
			o->o_x *= sc;
			o->o_y *= sc;
	/* NOTE!! may need to adjust center offset of text */
		}
		else if (o->o_type < TEXT) {	/* PIC couldn't scale text */
			for (i = 0; i < 6; i++)
				o->o_xform[i].f *= sc;
		}
		if (o->o_type == BLOCK)		/* skip the contents */
			o = o->o_val[N_VAL].o;
	}
	/* make arrowheads large to compensate for later shrinkage */
	for (o = objhead; o != objtail; o = o->o_next) {
		if ((o->o_type == LINE || o->o_type == SPLINE ||
		    o->o_type == ARROW) && (o->o_attr & HEAD12) != 0) {
			o->o_val[N_VAL+1].f *= picscale;
			o->o_val[N_VAL+2].f *= picscale;
		}
		if (o->o_type == ARC && (o->o_attr & HEAD12) != 0) {
			o->o_val[N_VAL+8].f *= picscale;
			o->o_val[N_VAL+9].f *= picscale;
		}
	}
	picscale = 1.0;

	Gbox[2] = Gbox[3] = -(Gbox[0] = Gbox[1] = 32767);
	for (o = objhead->o_next; o != objtail; o = o->o_next) {
		get_bounds(o, bnd, 1);
		track_bounds (bnd[0], bnd[1], bnd[2], bnd[3]);
		if (o->o_type == BLOCK)
			o = o->o_val[N_VAL].o;
	}
#if 0
	cur_xform[0].f /= v;
	cur_xform[1].f /= v;
	cur_xform[2].f /= v;
	cur_xform[3].f /= v;
	checkscale(v);
#endif
}

void
scale (obj *p, int n, obj *q)
{
register double	det;
	double	M[6];

	M[0] = exprlist[nexpr-n];
	M[3] = (n == 1 ? M[0] : exprlist[nexpr-n+1]);
	nexpr = 0;
	if ((det = M[0] * M[3]) == 0.0) {
		yyerror("can't scale by 0 factor");
		return;
	}
	M[1] = M[2] = M[4] = M[5] = 0.;
	if (q != NULL) {
		M[4] = (1 - M[0]) * Xformx(q,1,q->o_x,q->o_y);
		M[5] = (1 - M[3]) * Xformy(q,0,q->o_x,q->o_y);
	}
	apply(p, M, det);
}

void
olpscaleobj (obj *p, double sx, double sy, double cx, double cy)
/*        double sx, sy;    scale factor in x and y directions */
/*        double cx, cy;    center for scaling in real (inches) units */
{
register double	det;
       	 double	M[6];

	M[0] = sx;
	M[3] = sy;
	if ((det = M[0] * M[3]) == 0.0) {
		yyerror("can't scale by 0 factor");
		return;
	}
	M[1] = M[2] = 0.0;
        M[4] = cx * (1. - sx);
        M[5] = cy * (1. - sy);
        
	apply(p, M, det);
}

rotate (p, phi, q)
	obj	*p, *q;
	double	phi;	/* in radians */
{
register double	x, y;
	double	M[6];

	M[3] =   M[0] = Cos(phi);
	M[2] = -(M[1] = Sin(phi));
	if (q == NULL)
		M[4] = M[5] = 0;
	else {
		x = Xformx(q, 1, q->o_x, q->o_y);
		y = Xformy(q, 0, q->o_x, q->o_y);
		M[4] = (1 - M[0]) * x + M[1] * y;
		M[5] = (1 - M[0]) * y - M[1] * x;
	}
	apply(p, M, 1);
}

void 
rotate_point(x, y,  angle, cx, cy, pointx, pointy)
        double  x, y;
	double	angle;	/* in radians */
        double  cx, cy; /* center for rotation in screen units. */
        double *pointx, *pointy;    /* rotated point to be passed back */
{
   	double tempx, tempy;

        tempx = x - cx;
        tempy = y - cy;

        *pointx = (tempx * cos(angle)) - (tempy* sin(angle)) + cx;
        *pointy = (tempx * sin(angle)) + (tempy* cos(angle)) + cy;
}

void
olprotateobj (p, angle, cx, cy)
	obj	*p;
	double	angle;	/* in radians */
        double  cx, cy; /* center for rotation in real (inches) units. */
{
	double	M[6];

	M[3] =   M[0] = cos(angle);
	M[2] = -(M[1] = sin(angle));


	M[4] = (1 - M[0]) * cx + M[1] * cy;
	M[5] = (1 - M[0]) * cy - M[1] * cx;

	apply(p, M, 1);
}

xform (p, n)
	obj	*p;
	int	n;
{
register double	det;
	double	M[6];
	int	i;

	for (i = 0; i < n; ++i)
		M[i] = exprlist[i];
	while (i < 6)
		M[i++] = 0;
	nexpr = 0;
	if ((det = M[0]*M[3] - M[1]*M[2]) == 0)
		yyerror ("singular transformation matrix");
	else 
		apply(p, M, det);
}

reflect (p, q)
	obj	*p, *q;
{
	float	bnd[4];
	double	phi, c, s, x0, y0, x1, y1, M[6];
	int	axial;

	if (q == NULL)
		axial = x0 = x0 = x1 = y1 = 0;
	else
		switch (q->o_type) {
		case ARC:
		case ARROW:
		case LINE:
		case SPLINE:	whatpos(q, START, bnd, bnd+1);
				whatpos(q, END,   bnd+2, bnd+3);
				x0 = Xformx(q, 1, bnd[0], bnd[1]);
				y0 = Xformy(q, 0, bnd[0], bnd[1]);
				x1 = Xformx(q, 0, bnd[2], bnd[3]);
				y1 = Xformy(q, 0, bnd[2], bnd[3]);
				if (axial = (x0 != x1 || y0 != y1))
					break;
				/* fall through to default case; i.e., use  */
				/* center, for closed lines. otherwise, use */
				/* closest point to p along q as start of q */
#if 0      /*MS: The next few lines never get used */                                 
				S = (y1-y0)/(x1-x0);	/* slope of the line  */
				B = y0 - S*x0;		/* intercept term.    */
				s = -1/S;		/* perpendicular slope*/
				x = Xformx(p, 1, p->o_x, p->o_y);
				y = Xformy(p, 0, p->o_x, p->o_y);
				b = y - s*x;
				x0 = (b - B)/(S - s);	y0 = S*x0 + B;
#endif                                

		default:
				x0 = Xformx(q, 1, q->o_x, q->o_y);
				y0 = Xformy(q, 0, q->o_x, q->o_y);
				axial = 0;
		}
	if (!axial) {
		M[0] = M[3] = -1;
		M[1] = M[2] = 0;
		M[4] = 2 * x0;
		M[5] = 2 * y0;
	}
	else {
		if (x1 == x0 || y1 == y0) {	/* note: not both */
			s = 0;
			c = (x1 == x0 ? -1 : 1);
		}
		else if (fabs(x1-x0) == fabs(y1-y0)) {
			c = 0;
			s = (x1-x0 == y1-y0 ? 1 : -1);
		}
		else {
			c = cos(phi = 2 * atan2(y1-y0, x1-x0));
			s = sin(phi);
		}
		M[1] = M[2] = s;
		M[3] = -(M[0] = c);
		M[4] = x0*(1-c) - y0*s;
		M[5] = y0*(1+c) - x0*s;
	}
	apply(p, M, 1);
}

olpreflectobj (p, x0, y0, x1, y1)
	obj	*p;
        double  x0, y0, x1, y1;
{
	double	phi, c, s, M[6];
	int	axial;

        if  (x0 != x1 || y0 != y1)
           axial = 1;
        else    axial = 0;

	if (!axial) {
		M[0] = M[3] = -1;
		M[1] = M[2] = 0;
		M[4] = 2 * x0;
		M[5] = 2 * y0;
	}
	else {
		if (x1 == x0 || y1 == y0) {	/* note: not both */
			s = 0;
			c = (x1 == x0 ? -1 : 1);
		}
		else if (fabs(x1-x0) == fabs(y1-y0)) {
			c = 0;
			s = (x1-x0 == y1-y0 ? 1 : -1);
		}
		else {
			c = cos(phi = 2 * atan2(y1-y0, x1-x0));
			s = sin(phi);
		}
		M[1] = M[2] = s;
		M[3] = -(M[0] = c);
		M[4] = x0*(1-c) - y0*s;
		M[5] = y0*(1+c) - x0*s;
	}
	apply(p, M, 1);
}
