/*
 * Electric(tm) VLSI Design System
 *
 * File: usrwindow.c
 * User interface tool: public graphics routines
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "global.h"
#include "egraphics.h"
#include "efunction.h"
#include "usr.h"
#include "usrtrack.h"
#include "tecgen.h"
#include <math.h>

/***** REDRAW MODULES *****/

#define NOREDRAW ((REDRAW *)-1)

typedef struct Iredraw
{
	BOOLEAN          entryisnode;		/* true of object being re-drawn is node */
	union us_entry
	{
		NODEINST   *ni;
		ARCINST    *ai;
		void       *blind;
	} entryaddr;						/* object being re-drawn */
	struct Iredraw *nextredraw;			/* next in list */
} REDRAW;

static REDRAW    *us_redrawfree = NOREDRAW;	/* free list of re-draw modules */
static REDRAW    *us_firstredraw = NOREDRAW;	/* first in list of active re-draw modules */
static REDRAW    *us_firstopaque = NOREDRAW;	/* first in list of active opaque re-draws */
static REDRAW    *us_usedredraw = NOREDRAW;	/* first in list of used re-draw modules */

static REDRAW *us_allocredraw(void);
static void us_freeredraw(REDRAW*);

/***** 3D DISPLAY *****/

#define FOVMAX 170.0f
#define FOVMIN   2.0f

enum {ROTATEVIEW, ZOOMVIEW, PANVIEW};

typedef struct
{
	INTBIG    count;
	INTBIG    total;
	GRAPHICS *desc;
	INTBIG    depth;
	float    *x, *y, *z;
} POLY3D;

static BOOLEAN     us_3dgatheringpolys = 0;
static INTBIG      us_3dpolycount;
static INTBIG      us_3dpolytotal = 0;
static POLY3D    **us_3dpolylist;
static WINDOWPART *us_3dwindowpart;

/* interaction information */
static INTBIG      us_3dinteraction = ROTATEVIEW;
static INTBIG      us_3dinitialbuty;
static INTBIG      us_3dlastbutx, us_3dlastbuty;
static float       us_3dinitialfov;
static float       us_3dcenterx, us_3dcentery, us_3dcenterz;
static float       us_3dlowx, us_3dhighx, us_3dlowy, us_3dhighy, us_3dlowz, us_3dhighz;

static BOOLEAN us_3deachdown(INTBIG x, INTBIG y);
static void us_3dbuildtransform(XFORM3D *xf3);
static void us_3drender(WINDOWPART *w);
static POLY3D *us_3dgetnextpoly(INTBIG count);
static int us_3dpolydepthascending(const void *e1, const void *e2);

/***** MISCELLANEOUS *****/

#define ZSHIFT       4
#define ZUP(x)       ((x) << ZSHIFT)
#define ZDN(x)       (((x) + 1) >> ZSHIFT)

#define CROSSSIZE      2				/* size in pixels of half a cross */
#define BIGCROSSSIZE   5				/* size in pixels of half a big cross */
#define MINDISCSIZE    1				/* min radius of a disc */

extern GRAPHICS us_ebox;

/* prototypes for local routines */
static INTBIG us_showin(WINDOWPART*, GEOM*, NODEPROTO*, XARRAY, INTBIG, INTBIG);
static void us_queuevicinity(GEOM*, NODEPROTO*, XARRAY, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG);
static void us_wanttodrawo(INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, WINDOWPART*, GRAPHICS*, INTBIG);
static void us_drawextendedpolyline(POLYGON*, WINDOWPART*);
static void us_writetext(INTBIG, INTBIG, INTBIG, INTBIG, GRAPHICS*, char*, UINTBIG*, WINDOWPART*, TECHNOLOGY*);
static void us_reverse(POLYGON*);

/*
 * Routine to free all memory associated with this module.
 */
void us_freewindowmemory(void)
{
	REGISTER REDRAW *r;
	REGISTER POLY3D *p3;
	REGISTER INTBIG i;

	while (us_redrawfree != NOREDRAW)
	{
		r = us_redrawfree;
		us_redrawfree = us_redrawfree->nextredraw;
		efree((char *)r);
	}

	for(i=0; i<us_3dpolytotal; i++)
	{
		p3 = us_3dpolylist[i];
		if (p3->total > 0)
		{
			efree((char *)p3->x);
			efree((char *)p3->y);
			efree((char *)p3->z);
		}
		efree((char *)p3);
	}
	if (us_3dpolytotal > 0)
		efree((char *)us_3dpolylist);
}

/******************** WINDOW DISPLAY ********************/

/* routine to erase the facet in window "w" */
void us_clearwindow(WINDOWPART *w)
{
	REGISTER INTBIG newstate;

	startobjectchange((INTBIG)w, VWINDOWPART);
	(void)setval((INTBIG)w, VWINDOWPART, "curnodeproto", (INTBIG)NONODEPROTO, VNODEPROTO);
	newstate = (w->state & ~(GRIDON|WINDOWTYPE|WINDOWSIMULATING)) | DISPWINDOW;
	(void)setval((INTBIG)w, VWINDOWPART, "state", newstate, VINTEGER);
	(void)setval((INTBIG)w, VWINDOWPART, "buttonhandler", (INTBIG)DEFAULTBUTTONHANDLER, VADDRESS);
	(void)setval((INTBIG)w, VWINDOWPART, "charhandler", (INTBIG)DEFAULTCHARHANDLER, VADDRESS);
	(void)setval((INTBIG)w, VWINDOWPART, "changehandler", (INTBIG)DEFAULTCHANGEHANDLER, VADDRESS);
	(void)setval((INTBIG)w, VWINDOWPART, "termhandler", (INTBIG)DEFAULTTERMHANDLER, VADDRESS);
	(void)setval((INTBIG)w, VWINDOWPART, "redisphandler", (INTBIG)DEFAULTREDISPHANDLER, VADDRESS);
	endobjectchange((INTBIG)w, VWINDOWPART);
}

/* routine to erase the display of window "w" */
void us_erasewindow(WINDOWPART *w)
{
	static POLYGON *poly = NOPOLYGON;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_tool->cluster);

	maketruerectpoly(w->screenlx, w->screenhx, w->screenly, w->screenhy, poly);
	if ((w->state&INPLACEEDIT) != 0)
		xformpoly(poly, w->intofacet);
	poly->desc = &us_ebox;
	poly->style = FILLEDRECT;
	(*us_displayroutine)(poly, w);
}

/*
 * routine to begin editing facet "cur" in the current window.  The screen
 * extents of the facet are "lx", "hx", "ly", and "hy".  If "focusinst"
 * is not NONODEINST, then highlight that node and port "facetinstport"
 * (if it is not NOPORTPROTO) in the new window.  If "newframe" is true,
 * create a window for this facet, otherwise reuse the current one.
 */
void us_switchtofacet(NODEPROTO *cur, INTBIG lx, INTBIG hx, INTBIG ly, INTBIG hy,
	NODEINST *focusinst, PORTPROTO *facetinstport, BOOLEAN newframe, INTBIG pushpop)
{
	HIGHLIGHT newhigh;
	REGISTER INTBIG i, l, resethandlers, turnoffsim;
	INTBIG dummy;
	REGISTER VARIABLE *var;
	char *one[1];
	REGISTER INTBIG oldstate;
	REGISTER char *thisline, *location;
	REGISTER EDITOR *ed;
	WINDOWPART *neww, *oldw;

	us_clearhighlightcount();
	oldw = el_curwindowpart;
	turnoffsim = 0;
	if (oldw == NOWINDOWPART)
	{
		oldstate = DISPWINDOW;
		location = "Entire";
	} else
	{
		if ((oldw->state&WINDOWTYPE) == EXPLORERWINDOW)
		{
			for(neww = el_topwindowpart; neww != NOWINDOWPART; neww = neww->nextwindowpart)
				if (neww != oldw && neww->frame == oldw->frame) break;
			if (neww != NOWINDOWPART) oldw = neww;
		}
		if (pushpop == 0 && (oldw->state&WINDOWSIMULATING) != 0) turnoffsim = 1;
		if (turnoffsim != 0) us_setwindowsimulation(oldw, FALSE);
		oldstate = oldw->state;
		location = oldw->location;
	}
	startobjectchange((INTBIG)us_tool, VTOOL);

	if (!newframe)
	{
		/* delete current window and create a new window for this facet */
		neww = newwindowpart(location, oldw);
		if (neww == NOWINDOWPART) return;
		if (oldw != NOWINDOWPART)
		{
			if (turnoffsim != 0) us_setwindowsimulation(oldw, TRUE);
			killwindowpart(oldw);
		}
	} else
	{
		/* just create a new window for this facet */
		neww = us_wantnewwindow(0);
		if (neww == NOWINDOWPART)
		{
			us_abortcommand(_("Cannot create new window"));
			return;
		}
		oldstate = DISPWINDOW;
		location = "Entire";
	}
	el_curwindowpart = neww;

	if ((cur->cellview->viewstate&TEXTVIEW) != 0)
	{
		/* text window: make an editor */
		if (us_makeeditor(neww, describenodeproto(cur), &dummy, &dummy) == NOWINDOWPART)
			return;

		/* get the text that belongs here */
		var = getvalkey((INTBIG)cur, VNODEPROTO, VSTRING|VISARRAY, el_facet_message_key);
		if (var == NOVARIABLE)
		{
			one[0] = "";
			var = setvalkey((INTBIG)cur, VNODEPROTO, el_facet_message_key, (INTBIG)one,
				VSTRING|VISARRAY|(1<<VLENGTHSH));
			if (var == NOVARIABLE) return;
		}

		ed = neww->editor;
		ed->editobjaddr = (char *)cur;
		ed->editobjtype = VNODEPROTO;
		ed->editobjqual = "FACET_message";
		ed->editobjvar = var;

		/* load the text into the window */
		us_suspendgraphics(neww);
		l = getlength(var);
		for(i=0; i<l; i++)
		{
			thisline = ((char **)var->addr)[i];
			if (i == l-1 && *thisline == 0) continue;
			us_addline(neww, i, thisline);
		}
		us_resumegraphics(neww);

		/* setup for editing */
		neww->curnodeproto = cur;
		neww->changehandler = us_textfacetchanges;
	} else
	{
		neww->curnodeproto = cur;
		neww->editor = NOEDITOR;

		/* change the window type to be appropriate for editing */
		neww->state = (oldstate & ~WINDOWTYPE) | DISPWINDOW;
		resethandlers = 1;
		if (pushpop != 0)
		{
			if ((neww->state&WINDOWSIMULATING) != 0 &&
				((oldstate&WINDOWTYPE) == DISPWINDOW))
			{
				resethandlers = 0;
			}
		}

		/* adjust window if it changed from display window to something else */
		if ((neww->state&WINDOWTYPE) == DISPWINDOW && (oldstate&WINDOWTYPE) != DISPWINDOW)
		{
			/* became a display window: shrink the bottom and right edge */
			neww->usehx -= DISPLAYSLIDERSIZE;
			neww->usely += DISPLAYSLIDERSIZE;
		}
		if ((neww->state&WINDOWTYPE) != DISPWINDOW && (oldstate&WINDOWTYPE) == DISPWINDOW)
		{
			/* no longer a display window: shrink the bottom and right edge */
			neww->usehx += DISPLAYSLIDERSIZE;
			neww->usely -= DISPLAYSLIDERSIZE;
		}

		/* adjust window if it changed from waveform window to something else */
		if ((neww->state&WINDOWTYPE) == WAVEFORMWINDOW && (oldstate&WINDOWTYPE) != WAVEFORMWINDOW)
		{
			/* became a waveform window: shrink the bottom and right edge */
			neww->uselx += DISPLAYSLIDERSIZE;
			neww->usely += DISPLAYSLIDERSIZE;
		}
		if ((neww->state&WINDOWTYPE) != WAVEFORMWINDOW && (oldstate&WINDOWTYPE) == WAVEFORMWINDOW)
		{
			/* no longer a waveform window: shrink the bottom and right edge */
			neww->uselx -= DISPLAYSLIDERSIZE;
			neww->usely -= DISPLAYSLIDERSIZE;
		}

		if (resethandlers != 0)
		{
			neww->buttonhandler = DEFAULTBUTTONHANDLER;
			neww->changehandler = DEFAULTCHANGEHANDLER;
			neww->charhandler = DEFAULTCHARHANDLER;
			neww->termhandler = DEFAULTTERMHANDLER;
			neww->redisphandler = DEFAULTREDISPHANDLER;
		}

		us_squarescreen(neww, NOWINDOWPART, FALSE, &lx, &hx, &ly, &hy, pushpop);
		neww->screenlx = lx;
		neww->screenhx = hx;
		neww->screenly = ly;
		neww->screenhy = hy;
		computewindowscale(neww);

		/* window gets bigger: see if grid can be drawn */
		us_gridset(neww, neww->state);

		if (focusinst != NONODEINST)
		{
			newhigh.status = HIGHFROM;
			newhigh.facet = focusinst->parent;
			newhigh.fromgeom = focusinst->geom;
			newhigh.fromport = facetinstport;
			newhigh.frompoint = 0;
			newhigh.fromvar = NOVARIABLE;
			newhigh.fromvarnoeval = NOVARIABLE;
			us_addhighlight(&newhigh);
		}
	}

	(void)setval((INTBIG)el_curlib, VLIBRARY, "curnodeproto", (INTBIG)cur, VNODEPROTO);
	endobjectchange((INTBIG)us_tool, VTOOL);
	(void)setvalkey((INTBIG)us_tool, VTOOL, us_current_window_key, (INTBIG)neww,
		VWINDOWPART|VDONTSAVE);
}

/*
 * Routine to switch technologies.  If "forcedtechnology" is nonzero, switch to that
 * technology, otherwise determine technology from facet "facet".  If "always" is false,
 * only switch if the user has selected "auto-switching".
 */
void us_ensurepropertechnology(NODEPROTO *facet, char *forcedtechnology, BOOLEAN always)
{
	REGISTER USERCOM *uc;
	REGISTER TECHNOLOGY *tech;
	REGISTER WINDOWFRAME *curframe, *lastframe;

	/* if not auto-switching technologies, quit */
	if ((us_useroptions&AUTOSWITCHTECHNOLOGY) == 0) return;

	/* see if facet has a technology */
	if (facet == NONODEPROTO) return;
	if ((facet->cellview->viewstate&TEXTVIEW) != 0) return;
	tech = facet->tech;
	if (tech == gen_tech) return;
	if ((tech->userbits&NOPRIMTECHNOLOGY) != 0) return;

	/* switch technologies */
	lastframe = getwindowframe(TRUE);
	if (us_getmacro("pmtesetup") == NOVARIABLE) return;
	(void)initinfstr();
	(void)formatinfstr("pmtesetup \"%s\"", us_techname(facet));
	uc = us_makecommand(returninfstr());
	if (uc != NOUSERCOM)
	{
		us_execute(uc, FALSE, FALSE, FALSE);
		us_freeusercom(uc);
	}
	curframe = getwindowframe(TRUE);
	if (curframe != lastframe && lastframe != NOWINDOWFRAME)
		bringwindowtofront(lastframe);
}

/*
 * routine to initialize for changes to the screen
 */
void us_beginchanges(void)
{
}

/*
 * routine to finish making changes to the screen.  If "which" is NOWINDOWPART,
 * changes are made to all windows.  Otherwise, "which" is the specific
 * window to update.
 */
void us_endchanges(WINDOWPART *which)
{
	REGISTER WINDOWPART *w;
	XARRAY rottrans;
	REGISTER REDRAW *r, *nextr;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER INTBIG stopped;
	REGISTER INTBIG ret;

	stopped = el_pleasestop;
	for(r = us_firstredraw; r != NOREDRAW; r = nextr)
	{
		/* remember the next redraw module and queue this one for deletion */
		nextr = r->nextredraw;
		r->nextredraw = us_usedredraw;
		us_usedredraw = r;

		if (stopped != 0) continue;

		if (r->entryisnode)
		{
			ni = r->entryaddr.ni;
			if ((ni->userbits & (REWANTN|RETDONN|DEADN)) == REWANTN)
			{
				if (which != NOWINDOWPART)
				{
					if (ni->rotation == 0 && ni->transpose == 0)
					{
						ret = us_showin(which, ni->geom, ni->parent, el_matid, LAYERA, 1);
					} else
					{
						makerot(ni, rottrans);
						ret = us_showin(which, ni->geom, ni->parent, rottrans, LAYERA, 1);
					}
					if (ret < 0) stopped = 1; else
						if (ret & 2) us_queueopaque(ni->geom, FALSE);
				} else
				{
					for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
					{
						if ((w->state&WINDOWTYPE) != DISPWINDOW &&
							(w->state&WINDOWTYPE) != DISP3DWINDOW) continue;
						if (ni->rotation == 0 && ni->transpose == 0)
						{
							ret = us_showin(w, ni->geom, ni->parent, el_matid, LAYERA, 1);
						} else
						{
							makerot(ni, rottrans);
							ret = us_showin(w, ni->geom, ni->parent, rottrans, LAYERA, 1);
						}
						if (ret < 0) { stopped = 1;   break; }
						if (ret & 2) us_queueopaque(ni->geom, FALSE);
					}
				}
				ni->userbits |= RETDONN;
			}
		} else
		{
			ai = r->entryaddr.ai;
			if ((ai->userbits & (RETDONA|DEADA)) == 0)
			{
				if (which != NOWINDOWPART)
				{
					ret = us_showin(which, ai->geom, ai->parent, el_matid, LAYERA, 1);
					if (ret < 0) stopped = 1; else
						if (ret & 2) us_queueopaque(ai->geom, FALSE);
				} else
				{
					for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
					{
						if ((w->state&WINDOWTYPE) != DISPWINDOW &&
							(w->state&WINDOWTYPE) != DISP3DWINDOW) continue;
						ret = us_showin(w, ai->geom, ai->parent, el_matid, LAYERA, 1);
						if (ret < 0) { stopped = 1;   break; }
						if (ret & 2) us_queueopaque(ai->geom, FALSE);
					}
				}
				ai->userbits |= RETDONA;
			}
		}
	}
	us_firstredraw = NOREDRAW;

	/* now re-draw the opaque objects */
	for(r = us_firstopaque; r != NOREDRAW; r = nextr)
	{
		/* remember the next redraw module and queue this one for deletion */
		nextr = r->nextredraw;
		r->nextredraw = us_usedredraw;
		us_usedredraw = r;

		if (stopped != 0) continue;

		if (r->entryisnode)
		{
			ni = r->entryaddr.ni;
			if ((ni->userbits & (REWANTN|REODONN|DEADN)) == REWANTN)
			{
				if (which != NOWINDOWPART)
				{
					if (ni->rotation == 0 && ni->transpose == 0)
					{
						ret = us_showin(which, ni->geom, ni->parent, el_matid, LAYERA, 2);
					} else
					{
						makerot(ni, rottrans);
						ret = us_showin(which, ni->geom, ni->parent, rottrans, LAYERA, 2);
					}
					if (ret < 0) stopped = 1;
				} else
				{
					for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
					{
						if ((w->state&WINDOWTYPE) != DISPWINDOW &&
							(w->state&WINDOWTYPE) != DISP3DWINDOW) continue;
						if (ni->rotation == 0 && ni->transpose == 0)
						{
							ret = us_showin(w, ni->geom, ni->parent, el_matid, LAYERA, 2);
						} else
						{
							makerot(ni, rottrans);
							ret = us_showin(w, ni->geom, ni->parent, rottrans, LAYERA, 2);
						}
						if (ret < 0) { stopped = 1;   break; }
					}
				}
				ni->userbits |= REODONN;
			}
		} else
		{
			ai = r->entryaddr.ai;
			if ((ai->userbits & (REODONA|DEADA)) == 0)
			{
				if (which != NOWINDOWPART)
				{
					ret = us_showin(which, ai->geom, ai->parent, el_matid, LAYERA, 2);
					if (ret < 0) stopped = 1;
				} else
				{
					for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
					{
						if ((w->state&WINDOWTYPE) != DISPWINDOW &&
							(w->state&WINDOWTYPE) != DISP3DWINDOW) continue;
						ret = us_showin(w, ai->geom, ai->parent, el_matid, LAYERA, 2);
						if (ret < 0) { stopped = 1;   break; }
					}
				}
				ai->userbits |= REODONA;
			}
		}
	}
	us_firstopaque = NOREDRAW;

	/* now free up all of the redraw modules */
	for(r = us_usedredraw; r != NOREDRAW; r = nextr)
	{
		nextr = r->nextredraw;
		if (r->entryisnode)
		{
			ni = r->entryaddr.ni;
			ni->userbits &= ~(REWANTN|RELOCLN);
		} else
		{
			ai = r->entryaddr.ai;
			ai->userbits &= ~(REWANTA|RELOCLA);
		}
		us_freeredraw(r);
	}
	us_usedredraw = NOREDRAW;

	flushscreen();
}

/*
 * routine to make a change to the screen (sandwiched between a "us_beginchanges"
 * and an "us_endchanges" call.  Returns an indicator of what else needs to be
 * drawn (negative to stop display).
 */
INTBIG us_showin(WINDOWPART *w, GEOM *object, NODEPROTO *parnt, XARRAY trans, INTBIG on,
	INTBIG layers)
{
	REGISTER NODEINST *ni;
	XARRAY localtran, subrot, subtran;
	REGISTER INTBIG moretodo, res;
	REGISTER INTBIG objlocal;

	/* if the parent is the current facet in the window, draw the instance */
	moretodo = 0;
	if (parnt == w->curnodeproto)
	{
		if (object->entryisnode)
		{
			ni = object->entryaddr.ni;
			begintraversehierarchy();
			res = us_drawnodeinst(ni, on, trans, layers, w);
			if (res == -2) return(-1);
			if (res < 0) res = 0;
			moretodo |= res;
		} else
		{
			res = us_drawarcinst(object->entryaddr.ai, on, trans, layers, w);
			if (res < 0) return(-1);
			moretodo |= res;
		}

		/* if drawing the opaque layer, don't look for other things to draw */
		if (on && layers == 2) return(moretodo);

		/* queue re-drawing of objects in vicinity of this one */
		if (object->entryisnode)
			us_queuevicinity(object, parnt, trans, 0, 0, 0, 0, on); else
				us_queuevicinity(object, parnt, trans, 0, 0, 0, 0, on);
		return(moretodo);
	}

	/* look at all instances of the parent facet that are expanded */
	if (object->entryisnode)
		objlocal = object->entryaddr.ni->userbits & RELOCLN; else
			objlocal = object->entryaddr.ai->userbits & RELOCLA;

	/* Steve Holmlund of Factron suggested this next statement */
	if (on == 0) objlocal = 0;

	for(ni = parnt->firstinst; ni != NONODEINST; ni = ni->nextinst)
	{
		if ((ni->userbits & NEXPAND) == 0) continue;
		if (objlocal != 0 && (ni->userbits&(RELOCLN|REWANTN)) == 0) continue;

		/* transform nodeinst to outer instance */
		maketrans(ni, localtran);
		transmult(trans, localtran, subrot);
		if (ni->rotation == 0 && ni->transpose == 0)
		{
			res = us_showin(w, object, ni->parent, subrot, on, layers);
			if (res < 0) return(-1);
			moretodo |= res;
		} else
		{
			makerot(ni, localtran);
			transmult(subrot, localtran, subtran);
			res = us_showin(w, object, ni->parent, subtran, on, layers);
			if (res < 0) return(-1);
			moretodo |= res;
		}
	}
	return(moretodo);
}

/*
 * routine to queue object "p" to be re-drawn.  If "local" is true, only
 * draw the local instance of this object, not every instance.
 */
void us_queueredraw(GEOM *p, BOOLEAN local)
{
	REGISTER REDRAW *r;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;

	if (p->entryisnode)
	{
		ni = p->entryaddr.ni;
		ni->userbits = (ni->userbits & ~(RETDONN|REODONN|RELOCLN)) | REWANTN;
		if (local) ni->userbits |= RELOCLN;
	} else
	{
		ai = p->entryaddr.ai;
		ai->userbits = (ai->userbits & ~(RETDONA|REODONA|RELOCLA)) | REWANTA;
		if (local) ai->userbits |= RELOCLA;
	}

	/* queue this object for being re-drawn */
	r = us_allocredraw();
	if (r == NOREDRAW)
	{
		ttyputnomemory();
		return;
	}
	r->entryisnode = p->entryisnode;
	r->entryaddr.blind = p->entryaddr.blind;
	r->nextredraw = us_firstredraw;
	us_firstredraw = r;
}

/*
 * Routine to remove all redraw objects that are in library "lib"
 * (because the library has been deleted)
 */
void us_unqueueredraw(LIBRARY *lib)
{
	REGISTER REDRAW *r, *nextr, *lastr;
	REGISTER LIBRARY *rlib;

	lastr = NOREDRAW;
	for(r = us_firstredraw; r != NOREDRAW; r = nextr)
	{
		nextr = r->nextredraw;
		if (r->entryisnode) rlib = r->entryaddr.ni->parent->cell->lib; else
			rlib = r->entryaddr.ai->parent->cell->lib;
		if (rlib == lib)
		{
			if (lastr == NOREDRAW) us_firstredraw = nextr; else
				lastr->nextredraw = nextr;
			us_freeredraw(r);
			continue;
		}
		lastr = r;
	}
}

/*
 * routine to erase the display of "geom" in all windows (sandwiched between
 * "us_beginchanges" and "us_endchanges" calls)
 */
void us_undisplayobject(GEOM *geom)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER WINDOWPART *w;
	XARRAY rottrans;

	if (geom->entryisnode)
	{
		ni = geom->entryaddr.ni;
		for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		{
			if ((w->state&WINDOWTYPE) != DISPWINDOW &&
				(w->state&WINDOWTYPE) != DISP3DWINDOW) continue;
			if (ni->rotation == 0 && ni->transpose == 0)
			{
				(void)us_showin(w, geom, ni->parent, el_matid, LAYERN, 3);
			} else
			{
				makerot(ni, rottrans);
				(void)us_showin(w, geom, ni->parent, rottrans, LAYERN, 3);
			}
		}
	} else
	{
		ai = geom->entryaddr.ai;
		for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		{
			if ((w->state&WINDOWTYPE) != DISPWINDOW &&
				(w->state&WINDOWTYPE) != DISP3DWINDOW) continue;
			(void)us_showin(w, geom, ai->parent, el_matid, LAYERN, 3);
		}
	}
}

/******************** HELPER ROUTINES ******************/

/*
 * routine to queue the opaque layers of object "p" to be re-drawn.
 * If "local" is true, only draw the local instance of this object, not
 * every instance.
 */
void us_queueopaque(GEOM *p, BOOLEAN local)
{
	REGISTER REDRAW *r;
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *np;
	REGISTER ARCINST *ai;
	REGISTER INTBIG i;

	if (p->entryisnode)
	{
		ni = p->entryaddr.ni;

		/* count displayable variables on this node */
		for(i = 0; i < ni->numvar; i++)
			if ((ni->firstvar[i].type&VDISPLAY) != 0) break;

		/* if this nodeinst has nothing on the opaque layer, don't queue it */
		np = ni->proto;
		if (np->primindex != 0 && (np->userbits&NHASOPA) == 0 &&
			i >= ni->numvar && ni->firstportexpinst == NOPORTEXPINST) return;

		/* set the nodeinst bits for opaque redraw */
		ni->userbits = (ni->userbits & ~REODONN) | REWANTN;
		if (local) ni->userbits |= RELOCLN;
	} else
	{
		ai = p->entryaddr.ai;

		/* count displayable variables on this arc */
		for(i = 0; i < ai->numvar; i++)
			if ((ai->firstvar[i].type&VDISPLAY) != 0) break;

		/* if this arcinst has nothing on the opaque layer, don't queue it */
		if ((ai->proto->userbits&AHASOPA) == 0 && i >= ai->numvar) return;

		/* set the arcinst bits for opaque redraw */
		ai->userbits = (ai->userbits & ~REODONA) | REWANTA;
		if (local) ai->userbits |= RELOCLA;
	}

	/* queue the object for opaque redraw */
	r = us_allocredraw();
	if (r == NOREDRAW)
	{
		ttyputnomemory();
		return;
	}
	r->entryisnode = p->entryisnode;
	r->entryaddr.blind = p->entryaddr.blind;
	r->nextredraw = us_firstopaque;
	us_firstopaque = r;
}

/*
 * routine to allocate a new redraw from the pool (if any) or memory
 * routine returns NOREDRAW upon error
 */
REDRAW *us_allocredraw(void)
{
	REGISTER REDRAW *r;

	if (us_redrawfree == NOREDRAW)
	{
		r = (REDRAW *)emalloc(sizeof (REDRAW), us_tool->cluster);
		if (r == 0) return(NOREDRAW);
	} else
	{
		/* take module from free list */
		r = us_redrawfree;
		us_redrawfree = r->nextredraw;
	}
	return(r);
}

/*
 * routine to return redraw module "r" to the pool of free modules
 */
void us_freeredraw(REDRAW *r)
{
	r->nextredraw = us_redrawfree;
	us_redrawfree = r;
}

/*
 * routine to queue for local re-draw anything in facet "parnt" that is
 * in the vicinity of "object" with bounding box (lx-hx, ly-hy).  The objects
 * are to be drawn on if "on" is nonzero.  The transformation matrix
 * between the bounding box and the facet is in "trans".
 */
void us_queuevicinity(GEOM *object, NODEPROTO *parnt, XARRAY trans, INTBIG lx,
	INTBIG hx, INTBIG ly, INTBIG hy, INTBIG on)
{
	REGISTER GEOM *look;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	INTBIG bx, ux, by, uy;
	XARRAY localtran, xf, subrot;
	REGISTER BOOLEAN local;
	REGISTER INTBIG i, search, former;

	/* compute full extent of the object, including text */
	if (object != NOGEOM)
	{
		if (object->entryisnode)
		{
			ni = object->entryaddr.ni;
			us_getnodebounds(ni, &lx, &hx, &ly, &hy);
		} else
		{
			ai = object->entryaddr.ai;
			us_getarcbounds(ai, &lx, &hx, &ly, &hy);
		}
	}

	xform(lx, ly, &bx, &by, trans);
	xform(hx, hy, &ux, &uy, trans);
	if (bx > ux) { i = bx;  bx = ux;  ux = i; }
	if (by > uy) { i = by;  by = uy;  uy = i; }
	search = initsearch(bx, ux, by, uy, parnt);
	if (object == NOGEOM) local = TRUE; else local = FALSE;
	for(;;)
	{
		if ((look = nextobject(search)) == NOGEOM) break;

		/* don't re-draw the current object whose vicinity is being checked */
		if (look == object) continue;

		/* get the former inside factor (if it is a nodeinst) */
		if (look->entryisnode) former = look->entryaddr.ni->userbits;

		/* redraw all if object is going off, redraw opaque if going on */
		if (on == LAYERN) us_queueredraw(look, local); else
			us_queueopaque(look, local);

		/* that's all if the object is an arcinst */
		if (!look->entryisnode) continue;
		ni = look->entryaddr.ni;

		/* don't bother exploring if the nodeinst is already to be redrawn */
		if ((former & REWANTN) != 0) continue;

		/* explore the insides if the facet is expanded */
		if (ni->proto->primindex != 0 || (ni->userbits&NEXPAND) == 0) continue;

		/* explore the insides of this node and queue the pertinent parts */
		maketransI(ni, localtran);
		transmult(trans, localtran, xf);

		/* this is the former contents if "makerotI", which I think is wrong...smr */
		if (ni->transpose != 0) makeangle(ni->rotation, ni->transpose, localtran); else
			makeangle((3600 - ni->rotation)%3600, 0, localtran);
		localtran[2][0] = ni->proto->lowx + ni->proto->highx;
		localtran[2][1] = ni->proto->lowy + ni->proto->highy;
		xform(-localtran[2][0], -localtran[2][1], &localtran[2][0], &localtran[2][1], localtran);
		localtran[2][0] /= 2;    localtran[2][1] /= 2;
		transmult(xf, localtran, subrot);
		us_queuevicinity(NOGEOM, ni->proto, subrot, lx, hx, ly, hy, on);

		/* now flag this nodeinst so that it doesn't get completely redrawn */
		ni->userbits = (ni->userbits & ~REWANTN) | RELOCLN;
	}
}

/*
 * null routine for polygon display
 */
void us_nulldisplayroutine(POLYGON *obj, WINDOWPART *w)
{
}

/*
 * routine to write polygon "obj" in window "w".
 */
void us_showpoly(POLYGON *obj, WINDOWPART *w)
{
	REGISTER INTBIG i, cx, cy, rad;
	REGISTER INTBIG pre, tx, ty;
	INTBIG lx, ux, ly, uy, six, siy;
	WINDOWPART wsc;
	REGISTER GRAPHICS *gra;
	static POLYGON *objc = NOPOLYGON;
	INTBIG tsx, tsy;

	if (us_3dgatheringpolys)
	{
		us_3dshowpoly(obj, w);
		return;
	}

	/* quit if no bits to be written */
	if (obj->desc->bits == LAYERN) return;

	/* special case for grid display */
	if (obj->style == GRIDDOTS)
	{
		screendrawgrid(w, obj);
		return;
	}

	/* transform to space of this window */
	if ((w->state&INPLACEEDIT) != 0) xformpoly(obj, w->outoffacet);

	/* now draw the polygon */
	gra = obj->desc;
	switch (obj->style)
	{
		case FILLED:		/* filled polygon */
		case FILLEDRECT:	/* filled rectangle */
			if (isbox(obj, &lx, &ux, &ly, &uy))
			{
				/* simple rectangular box: transform, clip, and draw */
				if (us_makescreen(&lx, &ly, &ux, &uy, w)) break;
				screendrawbox(w, lx, ux, ly, uy, gra);

				/* for patterned and outlined rectangles, draw the box too */
				if ((gra->colstyle&(NATURE|OUTLINEPAT)) == (PATTERNED|OUTLINEPAT))
				{
					screendrawline(w, lx, ly, lx, uy, gra, 0);
					screendrawline(w, lx, uy, ux, uy, gra, 0);
					screendrawline(w, ux, uy, ux, ly, gra, 0);
					screendrawline(w, ux, ly, lx, ly, gra, 0);
				}
				break;
			}

			/* copy the polygon since it will be mangled when clipped */
			if (objc == NOPOLYGON) objc = allocstaticpolygon(obj->count, us_tool->cluster); else
				if (objc->limit < obj->count) (void)extendpolygon(objc, obj->count);
			objc->count = obj->count;
			objc->style = obj->style;
			objc->desc = gra;
			for(i=0; i<obj->count; i++)
			{
				objc->xv[i] = applyxscale(w, obj->xv[i]-w->screenlx) + w->uselx;
				objc->yv[i] = applyyscale(w, obj->yv[i]-w->screenly) + w->usely;
			}

			/* clip and draw the polygon */
			clippoly(objc, w->uselx, w->usehx, w->usely, w->usehy);
			if (objc->count <= 1) break;
			if (objc->count > 2)
			{
				/* always clockwise */
				if (areapoly(objc) < 0.0) us_reverse(objc);
				screendrawpolygon(w, objc->xv, objc->yv, objc->count, objc->desc);

				/* for patterned and outlined polygons, draw the outline too */
				if ((gra->colstyle&(NATURE|OUTLINEPAT)) == (PATTERNED|OUTLINEPAT))
				{
					for(i=0; i<objc->count; i++)
					{
						if (i == 0) pre = objc->count-1; else pre = i-1;
						screendrawline(w, objc->xv[pre], objc->yv[pre], objc->xv[i], objc->yv[i],
							objc->desc, 0);
					}
				}
			} else screendrawline(w, objc->xv[0], objc->yv[0], objc->xv[1], objc->yv[1],
				gra, 0);
			break;

		case CLOSEDRECT:		/* closed rectangle outline */
			us_wanttodraw(obj->xv[0], obj->yv[0], obj->xv[0], obj->yv[1], w, gra, 0);
			us_wanttodraw(obj->xv[0], obj->yv[1], obj->xv[1], obj->yv[1], w, gra, 0);
			us_wanttodraw(obj->xv[1], obj->yv[1], obj->xv[1], obj->yv[0], w, gra, 0);
			us_wanttodraw(obj->xv[1], obj->yv[0], obj->xv[0], obj->yv[0], w, gra, 0);
			break;

		case CROSSED:		/* polygon outline with cross */
			us_wanttodraw(obj->xv[0], obj->yv[0], obj->xv[2], obj->yv[2], w, gra, 0);
			us_wanttodraw(obj->xv[1], obj->yv[1], obj->xv[3], obj->yv[3], w, gra, 0);
			/* FALLTHROUGH */ 

		case CLOSED:		/* closed polygon outline */
			for(i=0; i<obj->count; i++)
			{
				if (i == 0) pre = obj->count-1; else pre = i-1;
				us_wanttodraw(obj->xv[pre], obj->yv[pre], obj->xv[i], obj->yv[i], w, gra, 0);
			}
			break;

		case OPENED:		/* opened polygon outline */
			for(i=1; i<obj->count; i++)
				us_wanttodraw(obj->xv[i-1], obj->yv[i-1], obj->xv[i], obj->yv[i], w, gra, 0);
			break;

		case OPENEDT1:		/* opened polygon outline, dotted */
			for(i=1; i<obj->count; i++)
				us_wanttodraw(obj->xv[i-1], obj->yv[i-1], obj->xv[i], obj->yv[i], w, gra, 1);
			break;

		case OPENEDT2:		/* opened polygon outline, dashed */
			for(i=1; i<obj->count; i++)
				us_wanttodraw(obj->xv[i-1], obj->yv[i-1], obj->xv[i], obj->yv[i], w, gra, 2);
			break;

		case OPENEDT3:		/* opened polygon outline, thicker */
			for(i=1; i<obj->count; i++)
				us_wanttodraw(obj->xv[i-1], obj->yv[i-1], obj->xv[i], obj->yv[i], w, gra, 3);
			break;

		case OPENEDO1:		/* extended opened polygon outline */
			us_drawextendedpolyline(obj, w);
			break;

		case VECTORS:		/* many lines */
			if (obj->count % 2 != 0)
				ttyputmsg(_("Cannot display vector with %ld vertices (must be even)"),
					obj->count);
			for(i=0; i<obj->count; i += 2)
				us_wanttodraw(obj->xv[i], obj->yv[i], obj->xv[i+1], obj->yv[i+1], w, gra, 0);
			break;

		case CROSS:		/* crosses (always have one point) */
		case BIGCROSS:
			getcenter(obj, &six, &siy);
			if (six < w->screenlx || six > w->screenhx || siy < w->screenly || siy > w->screenhy)
				break;
			if (obj->style == CROSS) i = CROSSSIZE; else i = BIGCROSSSIZE;
			us_wanttodrawo(six, -i, siy, 0, six, i, siy, 0, w, gra, 0);
			us_wanttodrawo(six, 0, siy, -i, six, 0, siy, i, w, gra, 0);
			break;

		case TEXTCENT:		/* text centered in box */
		case TEXTTOP:		/* text below top of box */
		case TEXTBOT:		/* text above bottom of box */
		case TEXTLEFT:		/* text right of left edge of box */
		case TEXTRIGHT:		/* text left of right edge of box */
		case TEXTTOPLEFT:	/* text to lower-right of upper-left corner */
		case TEXTBOTLEFT:	/* text to upper-right of lower-left corner */
		case TEXTTOPRIGHT:	/* text to lower-left of upper-right corner */
		case TEXTBOTRIGHT:	/* text to upper-left of lower-right corner */
			getbbox(obj, &lx, &ux, &ly, &uy);
			lx = applyxscale(w, lx-w->screenlx) + w->uselx;
			ly = applyyscale(w, ly-w->screenly) + w->usely;
			ux = applyxscale(w, ux-w->screenlx) + w->uselx;
			uy = applyyscale(w, uy-w->screenly) + w->usely;
			screensettextinfo(w, obj->tech, obj->textdescript);
			screengettextsize(w, obj->string, &tsx, &tsy);
			switch (obj->style)
			{
				case TEXTCENT:
					tx = (lx+ux-tsx) / 2;
					ty = (ly+uy-tsy) / 2;
					break;
				case TEXTTOP:
					tx = (lx+ux-tsx) / 2;
					ty = uy-tsy;
					break;
				case TEXTBOT:
					tx = (lx+ux-tsx) / 2;
					ty = ly;
					break;
				case TEXTLEFT:
					tx = lx;
					ty = (ly+uy-tsy) / 2;
					break;
				case TEXTRIGHT:
					tx = ux-tsx;
					ty = (ly+uy-tsy) / 2;
					break;
				case TEXTTOPLEFT:
					tx = lx;
					ty = uy-tsy;
					break;
				case TEXTBOTLEFT:
					tx = lx;
					ty = ly;
					break;
				case TEXTTOPRIGHT:
					tx = ux-tsx;
					ty = uy-tsy;
					break;
				case TEXTBOTRIGHT:
					tx = ux-tsx;
					ty = ly;
					break;
			}
			if (tx > w->usehx || tx+tsx < w->uselx ||
				ty > w->usehy || ty+tsy < w->usely) break;
			screendrawtext(w, tx, ty, obj->string, gra);
			break;

		case TEXTBOX:		/* text centered and contained in box */
			getbbox(obj, &lx, &ux, &ly, &uy);
			if (us_makescreen(&lx, &ly, &ux, &uy, w)) break;
			us_writetext(lx, ux, ly, uy, gra, obj->string, obj->textdescript, w, obj->tech);
			break;

		case CIRCLE:    case THICKCIRCLE:
		case CIRCLEARC: case THICKCIRCLEARC:
			/* must scale the window for best precision when drawing curves */
			wsc.screenlx = w->screenlx;
			wsc.screenly = w->screenly;
			wsc.screenhx = w->screenhx;
			wsc.screenhy = w->screenhy;
			wsc.uselx = ZUP(w->uselx);
			wsc.usely = ZUP(w->usely);
			wsc.usehx = ZUP(w->usehx);
			wsc.usehy = ZUP(w->usehy);
			computewindowscale(&wsc);

			/* get copy polygon */
			if (objc == NOPOLYGON) objc = allocstaticpolygon(obj->count, us_tool->cluster); else
				if (objc->limit < obj->count) (void)extendpolygon(objc, obj->count);

			/* transform and copy the polygon */
			objc->count = obj->count;
			objc->style = obj->style;
			for(i=0; i<obj->count; i++)
			{
				objc->xv[i] = applyxscale(&wsc, obj->xv[i]-wsc.screenlx) + wsc.uselx;
				objc->yv[i] = applyyscale(&wsc, obj->yv[i]-wsc.screenly) + wsc.usely;
			}

			/* clip the circle */
			cliparc(objc, wsc.uselx, wsc.usehx, wsc.usely, wsc.usehy);

			/* circle outline at [0] radius to [1] */
			switch (objc->style)
			{
				case CIRCLE:
					if (objc->count != 2) break;
					i = computedistance(objc->xv[0], objc->yv[0], objc->xv[1], objc->yv[1]);
					screendrawcircle(w, ZDN(objc->xv[0]), ZDN(objc->yv[0]), ZDN(i), gra);
					break;
				case THICKCIRCLE:
					if (objc->count != 2) break;
					i = computedistance(objc->xv[0], objc->yv[0], objc->xv[1], objc->yv[1]);
					screendrawthickcircle(w, ZDN(objc->xv[0]), ZDN(objc->yv[0]), ZDN(i), gra);
					break;
				case CIRCLEARC:
					/* thin arcs at [i] points [1+i] [2+i] clockwise */
					if (objc->count == 0) break;
					if ((objc->count%3) != 0) break;
					for (i=0; i<objc->count; i += 3)
						screendrawcirclearc(w, ZDN(objc->xv[i]), ZDN(objc->yv[i]), ZDN(objc->xv[i+1]),
							ZDN(objc->yv[1+i]), ZDN(objc->xv[i+2]), ZDN(objc->yv[i+2]), gra);
					break;
				case THICKCIRCLEARC:
					/* thick arcs at [i] points [1+i] [2+i] clockwise */
					if (objc->count == 0) break;
					if ((objc->count%3) != 0) break;
					for (i=0; i<objc->count; i += 3)
						screendrawthickcirclearc(w, ZDN(objc->xv[i]), ZDN(objc->yv[i]), ZDN(objc->xv[i+1]),
							ZDN(objc->yv[1+i]), ZDN(objc->xv[i+2]), ZDN(objc->yv[i+2]), gra);
			}
			break;

		case DISC:
			/* filled circle at [0] radius to [1] */
			if (obj->count != 2) break;
			cx = applyxscale(w, obj->xv[0]-w->screenlx) + w->uselx;
			cy = applyyscale(w, obj->yv[0]-w->screenly) + w->usely;
			six = applyxscale(w, obj->xv[1]-w->screenlx) + w->uselx;
			siy = applyyscale(w, obj->yv[1]-w->screenly) + w->usely;
			rad = computedistance(cx, cy, six, siy);
			if (rad <= MINDISCSIZE) rad = MINDISCSIZE;

			/* clip if completely off screen */
			if (cx + rad < w->uselx || cx - rad > w->usehx ||
				cy + rad < w->usely || cy - rad > w->usehy)
					break;
			screendrawdisc(w, cx, cy, rad, gra);
			break;
	}

	/* transform from space of this window */
	if ((w->state&INPLACEEDIT) != 0) xformpoly(obj, w->intofacet);
}

/*
 * routine to clip and possibly draw a line from (fx,fy) to (tx,ty) in
 * window "w" with description "desc", texture "texture"
 */
void us_wanttodraw(INTBIG fx, INTBIG fy, INTBIG tx, INTBIG ty, WINDOWPART *w, GRAPHICS *desc,
	INTBIG texture)
{
	fx = applyxscale(w, fx-w->screenlx) + w->uselx;
	fy = applyyscale(w, fy-w->screenly) + w->usely;
	tx = applyxscale(w, tx-w->screenlx) + w->uselx;
	ty = applyyscale(w, ty-w->screenly) + w->usely;
	if (clipline(&fx, &fy, &tx, &ty, w->uselx, w->usehx, w->usely, w->usehy)) return;
	screendrawline(w, fx, fy, tx, ty, desc, texture);
}

/*
 * routine to clip and possibly draw a line from (fx,fy) to (tx,ty) in
 * window "w" with description "desc", texture "texture"
 */
void us_wanttodrawo(INTBIG fx, INTBIG fxo, INTBIG fy, INTBIG fyo, INTBIG tx, INTBIG txo,
	INTBIG ty, INTBIG tyo, WINDOWPART *w, GRAPHICS *desc, INTBIG texture)
{
	fx = applyxscale(w, fx-w->screenlx) + w->uselx + fxo;
	fy = applyyscale(w, fy-w->screenly) + w->usely + fyo;
	tx = applyxscale(w, tx-w->screenlx) + w->uselx + txo;
	ty = applyyscale(w, ty-w->screenly) + w->usely + tyo;
	if (clipline(&fx, &fy, &tx, &ty, w->uselx, w->usehx, w->usely, w->usehy)) return;
	screendrawline(w, fx, fy, tx, ty, desc, texture);
}

/*
 * routine to draw an opened polygon full of lines, set out by 1 pixel.  The polygon is in "obj".
 */
void us_drawextendedpolyline(POLYGON *obj, WINDOWPART *w)
{
	REGISTER INTBIG i;
	REGISTER INTBIG x1, y1, x2, y2, x1o, y1o, x2o, y2o, centerx, centery, diff;
	INTBIG lx, hx, ly, hy;

	/* if polygon is a line, extension is easy */
	if (isbox(obj, &lx, &hx, &ly, &hy))
	{
		if (lx == hx)
		{
			us_wanttodrawo(lx, -1, ly, 0, lx, -1, hy, 0, w, obj->desc, 0);
			us_wanttodrawo(lx, 1, ly, 0, lx, 1, hy, 0, w, obj->desc, 0);
			return;
		}
		if (ly == hy)
		{
			us_wanttodrawo(lx, -1, ly, 0, hx, -1, ly, 0, w, obj->desc, 0);
			us_wanttodrawo(lx, 1, ly, 0, hx, 1, ly, 0, w, obj->desc, 0);
			return;
		}
	}

	if (obj->count == 3 && obj->xv[0] == obj->xv[2] && obj->yv[0] == obj->yv[2])
	{
		x1 = obj->xv[0];   y1 = obj->yv[0];
		x2 = obj->xv[1];   y2 = obj->yv[1];
		if (x1 == x2)
		{
			us_wanttodrawo(x1,-1, y1, 0, x2,-1, y2, 0, w, obj->desc, 0);
			us_wanttodrawo(x1, 1, y1, 0, x2, 1, y2, 0, w, obj->desc, 0);
			return;
		}
		if (y1 == y2)
		{
			us_wanttodrawo(x1, 0, y1,-1, x2, 0, y2,-1, w, obj->desc, 0);
			us_wanttodrawo(x1, 0, y1,1, x2, 0, y2, 1, w, obj->desc, 0);
			return;
		}
		if ((x1-x2) * (y1-y2) > 0)
		{
			us_wanttodrawo(x1,1, y1,-1, x2,1, y2,-1, w, obj->desc, 0);
			us_wanttodrawo(x1,-1, y1,1, x2,-1, y2,1, w, obj->desc, 0);
		} else
		{
			us_wanttodrawo(x1,1, y1,1, x2,1, y2,1, w, obj->desc, 0);
			us_wanttodrawo(x1,-1, y1,-1, x2,-1, y2,-1, w, obj->desc, 0);
		}
		return;
	}

	/* do extension about polygon (and see if the polygon is a single point) */
	centerx = centery = diff = 0;
	for(i=0; i<obj->count; i++)
	{
		centerx += obj->xv[i];   centery += obj->yv[i];
		if (obj->xv[i] != obj->xv[0]) diff++;
		if (obj->yv[i] != obj->yv[0]) diff++;
	}
	centerx /= obj->count;   centery /= obj->count;

	/* special case if a single point */
	if (diff == 0)
	{
		us_wanttodrawo(centerx, -1, centery, -1, centerx, 1, centery, -1, w, obj->desc, 0);
		us_wanttodrawo(centerx, 1, centery, -1, centerx, 1, centery, 1, w, obj->desc, 0);
		us_wanttodrawo(centerx, 1, centery, 1, centerx, -1, centery, 1, w, obj->desc, 0);
		us_wanttodrawo(centerx, -1, centery, 1, centerx, -1, centery, -1, w, obj->desc, 0);
		return;
	}

	for(i=1; i<obj->count; i++)
	{
		x1 = obj->xv[i-1];   y1 = obj->yv[i-1];
		x2 = obj->xv[i];     y2 = obj->yv[i];
		if (x1 < centerx) x1o = -1; else
			if (x1 > centerx) x1o = 1; else x1o = 0;
		if (y1 < centery) y1o = -1; else
			if (y1 > centery) y1o = 1; else y1o = 0;
		if (x2 < centerx) x2o = -1; else
			if (x2 > centerx) x2o = 1; else x2o = 0;
		if (y2 < centery) y2o = -1; else
			if (y2 > centery) y2o = 1; else y2o = 0;
		us_wanttodrawo(x1, x1o, y1, y1o, x2, x2o, y2, y2o, w, obj->desc,
			0);
	}
}

/*
 * Write text in a box.  The box ranges from "lx" to "ux" in X and
 * from "ly" to "uy" in Y.  Draw in bit planes "desc->bits" with color
 * "desc->color".  Put "txt" there (or as much as will fit).  The value of
 * "initialfont" is the default size of text which will be reduced until it
 * can fit.
 */
void us_writetext(INTBIG lx, INTBIG ux, INTBIG ly, INTBIG uy, GRAPHICS *desc, char *txt,
	UINTBIG *initdescript, WINDOWPART *win, TECHNOLOGY *tech)
{
	REGISTER INTBIG stop, save, xabssize, yabssize, abssize, newsize, oldsize;
	INTBIG six, siy;
	UINTBIG descript[TEXTDESCRIPTSIZE];

	/* scan for a font that fits */
	TDCOPY(descript, initdescript);
	for(;;)
	{
		screensettextinfo(win, tech, descript);
		screengettextsize(win, txt, &six, &siy);
		if (six <= ux-lx && siy <= uy-ly) break;

		oldsize = TDGETSIZE(descript);
		abssize = TXTGETPOINTS(oldsize);
		if (abssize != 0)
		{
			/* jump quickly to the proper font size */
			if (six <= ux-lx) xabssize = abssize; else
				xabssize = abssize * (ux-lx) / six;
			if (siy <= uy-ly) yabssize = abssize; else
				yabssize = abssize * (uy-ly) / siy;
			newsize = mini(xabssize, yabssize);
			if (newsize < 4) return;
			TDSETSIZE(descript, TXTSETPOINTS(newsize));
		} else
		{
			newsize = TXTGETQLAMBDA(TDGETSIZE(descript)) - 1;
			if (newsize <= 0) return;
			TDSETSIZE(descript, TXTSETQLAMBDA(newsize));
		}
	}

	/* if the text doesn't fit in Y, quit */
	if (siy > uy-ly) return;

	/* truncate in X if possible */
	if (six > ux-lx)
	{
		stop = (ux-lx) * strlen(txt);
		stop /= six;
		if (stop == 0) return;
		save = txt[stop];   txt[stop] = 0;
		screengettextsize(win, txt, &six, &siy);
	} else stop = -1;

	/* draw the text */
	screendrawtext(win, lx+(ux-lx-six)/2, ly+(uy-ly-siy)/2, txt, desc);
	if (stop >= 0) txt[stop] = (char)save;
}

/******************** WINDOW PANNING ********************/

/*
 * routine to slide the contents of the current window up by "dist" lambda
 * units (slides down if "dist" is negative)
 */
void us_slideup(INTBIG dist)
{
	/* save and erase highlighting */
	us_pushhighlight();
	us_clearhighlightcount();

	/* set the new window data */
	startobjectchange((INTBIG)el_curwindowpart, VWINDOWPART);
	(void)setval((INTBIG)el_curwindowpart, VWINDOWPART, "screenly", el_curwindowpart->screenly - dist, VINTEGER);
	(void)setval((INTBIG)el_curwindowpart, VWINDOWPART, "screenhy", el_curwindowpart->screenhy - dist, VINTEGER);
	endobjectchange((INTBIG)el_curwindowpart, VWINDOWPART);

	/* restore highlighting */
	us_pophighlight(FALSE);
}

/*
 * routine to slide the current window left by "dist" lambda units
 * (slides right if "dist" is negative)
 */
void us_slideleft(INTBIG dist)
{
	/* save and erase highlighting */
	us_pushhighlight();
	us_clearhighlightcount();

	startobjectchange((INTBIG)el_curwindowpart, VWINDOWPART);
	(void)setval((INTBIG)el_curwindowpart, VWINDOWPART, "screenlx", el_curwindowpart->screenlx + dist, VINTEGER);
	(void)setval((INTBIG)el_curwindowpart, VWINDOWPART, "screenhx", el_curwindowpart->screenhx + dist, VINTEGER);
	endobjectchange((INTBIG)el_curwindowpart, VWINDOWPART);

	/* restore highlighting */
	us_pophighlight(FALSE);
}

/******************** TRANSFORMATION TO SCREEN ********************/

/*
 * routine to convert the reference parameters (lx,ux, ly,uy)
 * which define a box to screen co-ordinates ready to plot.
 * The values are scaled to screen space of window "w" and clipped.
 * If the routine returns true, the box is all off the screen.
 */
BOOLEAN us_makescreen(INTBIG *lx, INTBIG *ly, INTBIG *ux, INTBIG *uy, WINDOWPART *w)
{
	/* transform to screen space */
	if (*ux < w->screenlx || *lx > w->screenhx) return(TRUE);
	if (*uy < w->screenly || *ly > w->screenhy) return(TRUE);
	*lx = applyxscale(w, *lx-w->screenlx) + w->uselx;
	*ly = applyyscale(w, *ly-w->screenly) + w->usely;
	*ux = applyxscale(w, *ux-w->screenlx) + w->uselx;
	*uy = applyyscale(w, *uy-w->screenly) + w->usely;

	/* now clip to screen bounds */
	if (*lx < w->uselx) *lx = w->uselx;
	if (*ly < w->usely) *ly = w->usely;
	if (*ux > w->usehx) *ux = w->usehx;
	if (*uy > w->usehy) *uy = w->usehy;
	return(FALSE);
}

/*
 * reverse the edge order of a polygon
 */
void us_reverse(POLYGON *poly)
{
	REGISTER INTBIG i, invi;
	REGISTER INTBIG mx, my;

	for (i = 0, invi = poly->count-1; i<invi; i++, invi--)
	{
		mx = poly->xv[invi];
		my = poly->yv[invi];
		poly->xv[invi] = poly->xv[i];
		poly->yv[invi] = poly->yv[i];
		poly->xv[i] = mx;
		poly->yv[i] = my;
	}
}

/******************** 3D DISPLAY ********************/

/*
 * Routine to setup the transformation matrix for 3D viewing.
 * Called once when the display is initially converted to 3D.
 */
void us_3dsetupviewing(WINDOWPART *w)
{
	REGISTER TECHNOLOGY *tech;
	REGISTER INTBIG i, lowheight, highheight, lambda;
	INTBIG thickness, height;
	float scale, cx, cy, cz, sx, sy, sz;
	REGISTER NODEPROTO *np;
	REGISTER XFORM3D *xf3;

	/* determine height range */
	lowheight = 0;   highheight = -1;
	for(tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
	{
		for(i=0; i<tech->layercount; i++)
		{
			if (get3dfactors(tech, i, &height, &thickness)) continue;
			if (highheight < lowheight)
			{
				highheight = lowheight = height;
			} else
			{
				if (height < lowheight) lowheight = height;
				if (height > highheight) highheight = height;
			}
		}
	}

	/* setup initial camera */
	np = w->curnodeproto;
	if (np == NONODEPROTO) return;
	cx = (np->lowx + np->highx) / 2.0f;
	cy = (np->lowy + np->highy) / 2.0f;
	lambda = el_curlib->lambda[el_curtech->techindex];
	cz = (float)((highheight + lowheight) / 2 * lambda);
	sx = (float)(np->highx - np->lowx);
	sy = (float)(np->highy - np->lowy);
	sz = (float)((highheight - lowheight) * lambda);
	scale = (sx > sy) ? sx : sy;
	scale = (sz > scale) ? sz : scale;

	/* setup viewing parameters */
	xf3 = &w->xf3;
	xf3->fieldofview = 45.0f;
	xf3->eye[0] = -0.2f;   xf3->eye[1] = 0.2f;   xf3->eye[2] = 1.0f;
	vectornormalize3d(xf3->eye);
	vectormultiply3d(xf3->eye, scale, xf3->eye);
	xf3->eye[0] += cx;   xf3->eye[1] += cy;   xf3->eye[2] += cz;
	xf3->view[0] = cx;   xf3->view[1] = cy;   xf3->view[2] = cz;
	xf3->up[0] = 0.0;   xf3->up[1] = 1.0;   xf3->up[2] = 0.0;
	xf3->nearplane = 0.1f;   xf3->farplane = scale * 60.0f;
	xf3->screenx = (float)(w->usehx - w->uselx) / 2.0f;
	xf3->screeny = (float)(w->usehy - w->usely) / 2.0f;
	xf3->aspect = xf3->screenx / xf3->screeny;
	us_3dbuildtransform(xf3);
}

/*
 * Routine to fill window "win".
 */
void us_3dfillview(WINDOWPART *win)
{
	XFORM3D *xf3;
	float sx, sy, sz, maxs, toeye[3];

	xf3 = &win->xf3;

	sx = us_3dhighx - us_3dlowx;
	sy = us_3dhighy - us_3dlowy;
	sz = us_3dhighz - us_3dlowz;
	maxs = (sx > sy) ? sx : sy;
	maxs = (sz > maxs) ? sz : maxs;

	xf3->fieldofview = 45.0f;
	vectorsubtract3d(xf3->eye, xf3->view, toeye);
	xf3->view[0] = us_3dcenterx;  xf3->view[1] = us_3dcentery;  xf3->view[2] = us_3dcenterz;
	vectoradd3d(xf3->view, toeye, xf3->eye);
	xf3->nearplane = 0.1f;
	xf3->farplane = maxs * 60.0f;

	us_3dbuildtransform(xf3);
	us_3drender(win);
}

/*
 * Routine to zoom window "win" by a factor of "z".
 */
void us_3dzoomview(WINDOWPART *win, float z)
{
	XFORM3D *xf3;

	xf3 = &win->xf3;
	xf3->fieldofview = xf3->fieldofview * z;
	if (xf3->fieldofview > FOVMAX) xf3->fieldofview = FOVMAX;
	if (xf3->fieldofview < FOVMIN) xf3->fieldofview = FOVMIN;
	us_3dbuildtransform(xf3);
	us_3drender(win);
}

/*
 * Routine to pan window "win" by a factor of "x,y".
 */
void us_3dpanview(WINDOWPART *win, INTBIG x, INTBIG y)
{
	XFORM3D *xf3;
	float d[3], side[3], up[3], view[3], scale, e[3], offset[3];

	xf3 = &win->xf3;
	vectorsubtract3d(xf3->view, xf3->eye, d);
	vectorcross3d(d, xf3->up, side);
	vectorcross3d(d, side, up);
	vectornormalize3d(side);
	vectornormalize3d(up);
	vectorsubtract3d(xf3->view, xf3->eye, view);
	scale = vectormagnitude3d(view);
	vectormultiply3d(side, scale * 0.1f * (float)x, d);
	vectormultiply3d(up, scale * 0.1f * (float)y, e);
	vectoradd3d(d, e, offset);
	vectoradd3d(xf3->eye, offset, xf3->eye);
	vectoradd3d(xf3->view, offset, xf3->view);

	us_3dbuildtransform(xf3);
	us_3drender(win);
}

/*
 * Routine to build the 4x4 transformation matrix in "xf3" from
 * the viewing parameters there.
 */
void us_3dbuildtransform(XFORM3D *xf3)
{
	float f[3], s[3], u[3], persp[4][4], xform[4][4], trans[4][4], rot[4][4], ff;

	/* build the perspective transform */
	matrixid3d(persp);
	if ((us_useroptions&NO3DPERSPECTIVE) == 0)
	{
		ff = 1.0f / (float)tan((xf3->fieldofview * EPI / 180.0f) / 2.0f);
		persp[0][0] = ff / xf3->aspect;
		persp[1][1] = ff;
		persp[2][2] = (xf3->farplane + xf3->nearplane) / (xf3->nearplane - xf3->farplane);
		persp[2][3] = 2.0f * xf3->farplane * xf3->nearplane / (xf3->nearplane - xf3->farplane);
		persp[3][2] = -1.0f;
		persp[3][3] = 0.0f;
	} else
	{
		persp[0][0] = 45.0f / xf3->fieldofview;
		persp[1][1] = 45.0f / xf3->fieldofview;
		persp[2][2] = -1.0;
	}

	/* build the viewing transform */
	matrixid3d(trans);
	trans[0][3] = -xf3->eye[0];
	trans[1][3] = -xf3->eye[1];
	trans[2][3] = -xf3->eye[2];

	vectorsubtract3d(xf3->view, xf3->eye, f);
	vectornormalize3d(f);
	vectornormalize3d(xf3->up);
	vectorcross3d(f, xf3->up, s);
	vectorcross3d(s, f, u);
	matrixid3d(rot);
	rot[0][0] = s[0];   rot[0][1] = s[1];   rot[0][2] = s[2];
	rot[1][0] = u[0];   rot[1][1] = u[1];   rot[1][2] = u[2];
	rot[2][0] = -f[0];  rot[2][1] = -f[1];  rot[2][2] = -f[2];
	matrixmult3d(trans, rot, xform);

	/* build the transformation matrix */
	matrixmult3d(xform, persp, xf3->xform);
}

/*
 * Routine called at the start of drawing.
 */
void us_3dstartdrawing(WINDOWPART *win)
{
	us_3dgatheringpolys = TRUE;
	us_3dpolycount = 0;
	us_3dwindowpart = win;
}

/*
 * Helper routine for "us_3denddrawing()" that makes polygon depth go in ascending order
 */
int us_3dpolydepthascending(const void *e1, const void *e2)
{
	REGISTER POLY3D *c1, *c2;

	c1 = *((POLY3D **)e1);
	c2 = *((POLY3D **)e2);
	return(c1->depth - c2->depth);
}

/*
 * Routine called at the end of drawing.
 */
void us_3denddrawing(void)
{
	REGISTER POLY3D *poly1;
	REGISTER INTBIG i, j;

	/* flush the opaque graphics out */
	us_endchanges(NOWINDOWPART);
	us_3dgatheringpolys = FALSE;

	/* sort the polygons */
	esort(us_3dpolylist, us_3dpolycount, sizeof (POLY3D *), us_3dpolydepthascending);

	/* determine bounding volume */
	for(i=0; i<us_3dpolycount; i++)
	{
		poly1 = us_3dpolylist[i];
		for(j=0; j<poly1->count; j++)
		{
			if (i == 0 && j == 0)
			{
				us_3dlowx = us_3dhighx = poly1->x[j];
				us_3dlowy = us_3dhighy = poly1->y[j];
				us_3dlowz = us_3dhighz = poly1->z[j];
			} else
			{
				if (poly1->x[j] < us_3dlowx) us_3dlowx = poly1->x[j];
				if (poly1->x[j] > us_3dhighx) us_3dhighx = poly1->x[j];
				if (poly1->y[j] < us_3dlowy) us_3dlowy = poly1->y[j];
				if (poly1->y[j] > us_3dhighy) us_3dhighy = poly1->y[j];
				if (poly1->z[j] < us_3dlowz) us_3dlowz = poly1->z[j];
				if (poly1->z[j] > us_3dhighz) us_3dhighz = poly1->z[j];
			}
		}
	}
	us_3dcenterx = (us_3dlowx + us_3dhighx) / 2.0f;
	us_3dcentery = (us_3dlowy + us_3dhighy) / 2.0f;
	us_3dcenterz = (us_3dlowz + us_3dhighz) / 2.0f;

	/* render it */
	us_3dbuildtransform(&us_3dwindowpart->xf3);
	us_3drender(us_3dwindowpart);
}

/*
 * Routine to re-render the polygons to window "w".
 */
void us_3drender(WINDOWPART *w)
{
	REGISTER INTBIG i, j, k, passes, lambda;
	INTBIG start[2], finish[2], incr[2];
	REGISTER INTBIG save, isneg;
	POLY3D *poly3d;
	float vec[4], res[4], res2[4];
	float zplane, res3[4];
	static POLYGON *poly = NOPOLYGON;
	REGISTER XFORM3D *xf3;

	xf3 = &w->xf3;
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_tool->cluster);

	/* clear the screen */
	us_erasewindow(w);

	/* see if the view is from the back */
	lambda = el_curlib->lambda[el_curtech->techindex];
	for(zplane = us_3dhighz; zplane >= us_3dlowz; zplane -= lambda)
	{
		vec[0] = us_3dcenterx; vec[1] = us_3dcentery; vec[2] = zplane; vec[3] = 1.0;
		matrixxform3d(vec, xf3->xform, res);
		vec[0] = us_3dcenterx+1000.0f; vec[1] = us_3dcentery; vec[2] = zplane; vec[3] = 1.0;
		matrixxform3d(vec, xf3->xform, res2);
		vec[0] = us_3dcenterx; vec[1] = us_3dcentery+1000.0f; vec[2] = zplane; vec[3] = 1.0;
		matrixxform3d(vec, xf3->xform, res3);
		res[0] /= res[3];   res[1] /= res[3];   res[2] /= res[3];
		res2[0] /= res2[3];   res2[1] /= res2[3];   res2[2] /= res2[3];
		res3[0] /= res3[3];   res3[1] /= res3[3];   res3[2] /= res3[3];
		vectorsubtract3d(res2, res, res2);
		vectorsubtract3d(res3, res, res3);
		vectorcross3d(res2, res3, res);
		if (res[2] > 0) break;
	}
	if (zplane == us_3dhighz)
	{
		start[0] = 0;   finish[0] = us_3dpolycount;   incr[0] = 1;
		passes = 1;
	} else if (zplane < us_3dlowz)
	{
		start[0] = us_3dpolycount-1;   finish[0] = -1;   incr[0] = -1;
		passes = 1;
	} else
	{
		for(i=0; i<us_3dpolycount; i++)
			if (us_3dpolylist[i]->depth > zplane) break;
		start[0] = 0;                  finish[0] = i;	  incr[0] = 1;
		start[1] = us_3dpolycount-1;   finish[1] = i-1;   incr[1] = -1;
		passes = 2;
	}

	/* now draw it all */
	for(k=0; k<passes; k++)
	{
		for(i=start[k]; i != finish[k]; i = i + incr[k])
		{
			poly3d = us_3dpolylist[i];
			if (poly->limit < poly3d->count) (void)extendpolygon(poly, poly3d->count);
			isneg = 0;
			for(j=0; j<poly3d->count; j++)
			{
				vec[0] = poly3d->x[j];
				vec[1] = poly3d->y[j];
				vec[2] = poly3d->z[j];
				vec[3] = 1.0;
				matrixxform3d(vec, xf3->xform, res);
				if (res[2] < 0.0)
				{
					isneg = 1;
					break;
				}
				poly->xv[j] = (INTBIG)(res[0] / res[3] / 2.0 * xf3->screenx + xf3->screenx);
				poly->yv[j] = (INTBIG)(res[1] / res[3] / 2.0 * xf3->screeny + xf3->screeny);
				poly->xv[j] = roundfloat((poly->xv[j] - w->uselx) / w->scalex) + w->screenlx;
				poly->yv[j] = roundfloat((poly->yv[j] - w->usely) / w->scaley) + w->screenly;
			}
			if (isneg != 0) continue;
			poly->desc = poly3d->desc;
			poly->count = poly3d->count;
			poly->style = FILLED;
			save = poly3d->desc->bits;
			poly3d->desc->bits = LAYERA;
			(*us_displayroutine)(poly, w);
			poly3d->desc->bits = save;
		}
	}
}

void us_3dsetinteraction(INTBIG interaction)
{
	switch (interaction)
	{
		case 0: us_3dinteraction = ROTATEVIEW;  break;
		case 1: us_3dinteraction = ZOOMVIEW;    break;
		case 2: us_3dinteraction = PANVIEW;     break;
	}
}

/*
 * button handler for 3D windows
 */
void us_3dbuttonhandler(WINDOWPART *w, INTBIG but, INTBIG x, INTBIG y)
{
	REGISTER XFORM3D *xf3;

	/* changes to the mouse-wheel are handled by the user interface */
	if (wheelbutton(but))
	{
		us_buttonhandler(w, but, x, y);
		return;
	}
	xf3 = &w->xf3;
	us_3dlastbutx = x;
	us_3dlastbuty = us_3dinitialbuty = y;
	switch (us_3dinteraction)
	{
		case ROTATEVIEW:
			break;

		case ZOOMVIEW:
			us_3dinitialfov = xf3->fieldofview;
			break;

		case PANVIEW:
			break;
	}
	trackcursor(FALSE, us_nullup, us_nullvoid, us_3deachdown, us_nullchar, us_nullvoid, TRACKNORMAL);
}

BOOLEAN us_3deachdown(INTBIG x, INTBIG y)
{
	float d[3], e[3], offset[3], side[3], up[3], view[3], scale;
	float angle, sinTheta, cosTheta, toDirection[3], tempVector1[3], tempVector2[3],
		tempVector3[3], rotateVector[3], toLength, newFrom[3], sinPhi, cosPhi,
		newToDirection[3], rightDirection[3], dot;
	REGISTER XFORM3D *xf3;

	xf3 = &us_3dwindowpart->xf3;
	switch (us_3dinteraction)
	{
		case ROTATEVIEW:
			if (x == us_3dlastbutx && y == us_3dlastbuty) break;

			vectorsubtract3d(xf3->eye, xf3->view, toDirection);
			toLength = vectormagnitude3d(toDirection);
			vectornormalize3d(toDirection);

			/* Calculate orthonormal up direction by Gram-Schmidt orthogonalization */
			dot = vectordot3d(xf3->up, toDirection);
			up[0] = xf3->up[0] - dot * toDirection[0];
			up[1] = xf3->up[1] - dot * toDirection[1];
			up[2] = xf3->up[2] - dot * toDirection[2];

			/* orthonormal up vector vector */
			vectornormalize3d(up);	

			/* Calculate orthonormal right vector to make up an orthonormal view frame */
			vectorcross3d(up, toDirection, rightDirection);

			angle = (float)(180.0f * ((float)(us_3dlastbuty - y) /
				(float)(us_3dwindowpart->usehy-us_3dwindowpart->usely)/2.0f) * EPI / 180.0f);
			sinTheta = (float)sin(angle);
			cosTheta = (float)cos(angle);
			vectormultiply3d(toDirection, cosTheta, tempVector1);
			vectormultiply3d(xf3->up, sinTheta, tempVector2);
			vectoradd3d(tempVector1, tempVector2, rotateVector);
			vectormultiply3d(rotateVector, toLength, rotateVector);
			vectoradd3d(xf3->view, rotateVector, newFrom);

			/* rotate using RIGHT and new TO directions and X position of mouse */
			angle = (float)(180.0f * ((float)(us_3dlastbutx - x) /
				(float)(us_3dwindowpart->usehx-us_3dwindowpart->uselx)/2.0f) * EPI/180.0f);
			sinPhi = (float)sin(angle);
			cosPhi = (float)cos(angle);
			vectorsubtract3d(newFrom, xf3->view, newToDirection);
			vectornormalize3d(newToDirection);
			vectormultiply3d(newToDirection, cosPhi, tempVector1);
			vectormultiply3d(rightDirection, sinPhi, tempVector2);
			vectoradd3d(tempVector1, tempVector2, rotateVector);
			vectormultiply3d(rotateVector, toLength, rotateVector);
			vectoradd3d(xf3->view, rotateVector, xf3->eye);

			/* calculate new UP vector */
			vectormultiply3d(xf3->up, cosTheta, tempVector2);
			vectormultiply3d(toDirection, -sinTheta*cosPhi, tempVector1);
			vectormultiply3d(rightDirection, sinTheta*sinPhi, tempVector3);
			vectoradd3d(tempVector1, tempVector2, xf3->up);
			vectoradd3d(xf3->up, tempVector3, xf3->up);

			us_3dlastbutx = x;
			us_3dlastbuty = y;
			us_3dbuildtransform(xf3);
			us_3drender(us_3dwindowpart);
			break;

		case ZOOMVIEW:
			xf3->fieldofview = us_3dinitialfov + 0.1f * (float)(us_3dinitialbuty - y);
			if (xf3->fieldofview > FOVMAX) xf3->fieldofview = FOVMAX;
			if (xf3->fieldofview < FOVMIN) xf3->fieldofview = FOVMIN;
			us_3dbuildtransform(xf3);
			us_3drender(us_3dwindowpart);
			break;

		case PANVIEW:
			if (x == us_3dlastbutx && y == us_3dlastbuty) break;
			vectorsubtract3d(xf3->view, xf3->eye, d);
			vectorcross3d(d, xf3->up, side);
			vectorcross3d(d, side, up);
			vectornormalize3d(side);
			vectornormalize3d(up);
			vectorsubtract3d(xf3->view, xf3->eye, view);
			scale = vectormagnitude3d(view);
			vectormultiply3d(side, scale * 0.01f * (float)(us_3dlastbutx - x), d);
			vectormultiply3d(up, scale * 0.01f * (float)(y - us_3dlastbuty), e);
			vectoradd3d(d, e, offset);
			vectoradd3d(xf3->eye, offset, xf3->eye);
			vectoradd3d(xf3->view, offset, xf3->view);

			us_3dbuildtransform(xf3);
			us_3drender(us_3dwindowpart);
			us_3dlastbutx = x;
			us_3dlastbuty = y;
			break;
	}
	return(FALSE);
}

/*
 * Routine to draw polygon "poly" in window "w".
 */
void us_3dshowpoly(POLYGON *poly, WINDOWPART *w)
{
	REGISTER INTBIG i, previ, topdepth, botdepth, centerdepth, lambda;
	float topheight, botheight;
	INTBIG lx, hx, ly, hy, thickness, depth;
	REGISTER POLY3D *poly3d;

	/* ignore polygons with no color */
	if (poly->desc->col == 0) return;

	/* special case when drawing instance boundaries */
	if (poly->desc->col == el_colfacet)
	{
		(void)get3dfactors(el_curtech, 0, &depth, &thickness);
		topdepth = depth;
		topheight = (float)topdepth;
		if (isbox(poly, &lx, &hx, &ly, &hy))
		{
			poly3d = us_3dgetnextpoly(2);
			if (poly3d == 0) return;
			poly3d->x[0] = (float)lx;   poly3d->y[0] = (float)ly;   poly3d->z[0] = topheight;
			poly3d->x[1] = (float)lx;   poly3d->y[1] = (float)hy;   poly3d->z[1] = topheight;
			poly3d->depth = topdepth;
			poly3d->desc = poly->desc;

			poly3d = us_3dgetnextpoly(2);
			if (poly3d == 0) return;
			poly3d->x[0] = (float)lx;   poly3d->y[0] = (float)hy;   poly3d->z[0] = topheight;
			poly3d->x[1] = (float)hx;   poly3d->y[1] = (float)hy;   poly3d->z[1] = topheight;
			poly3d->depth = topdepth;
			poly3d->desc = poly->desc;

			poly3d = us_3dgetnextpoly(2);
			if (poly3d == 0) return;
			poly3d->x[0] = (float)hx;   poly3d->y[0] = (float)hy;   poly3d->z[0] = topheight;
			poly3d->x[1] = (float)hx;   poly3d->y[1] = (float)ly;   poly3d->z[1] = topheight;
			poly3d->depth = topdepth;
			poly3d->desc = poly->desc;

			poly3d = us_3dgetnextpoly(2);
			if (poly3d == 0) return;
			poly3d->x[0] = (float)hx;   poly3d->y[0] = (float)ly;   poly3d->z[0] = topheight;
			poly3d->x[1] = (float)lx;   poly3d->y[1] = (float)ly;   poly3d->z[1] = topheight;
			poly3d->depth = topdepth;
			poly3d->desc = poly->desc;
		}
		return;
	}

	/* make sure there is a technology and a layer for this polygon */
	if (poly->tech == NOTECHNOLOGY) return;
	if (get3dfactors(poly->tech, poly->layer, &depth, &thickness)) return;

	/* setup a 3D polygon */
	lambda = el_curlib->lambda[el_curtech->techindex];
	topdepth = (depth + thickness/2) * lambda;
	topheight = (float)topdepth;
	if (thickness != 0)
	{
		topdepth++;
		centerdepth = depth * lambda;
		botdepth = (depth - thickness/2) * lambda - 1;
		botheight = (float)((depth - thickness/2) * lambda);
	}

	/* fill the 3D polygon points */
	switch (poly->style)
	{
		case FILLED:		/* filled polygon */
		case FILLEDRECT:	/* filled rectangle */
		case CLOSEDRECT:	/* closed rectangle outline */
		case CLOSED:		/* closed polygon outline */
		case CROSSED:		/* polygon outline with cross */
			if (isbox(poly, &lx, &hx, &ly, &hy))
			{
				poly3d = us_3dgetnextpoly(4);
				if (poly3d == 0) return;
				poly3d->x[0] = (float)lx;   poly3d->y[0] = (float)ly;   poly3d->z[0] = topheight;
				poly3d->x[1] = (float)lx;   poly3d->y[1] = (float)hy;   poly3d->z[1] = topheight;
				poly3d->x[2] = (float)hx;   poly3d->y[2] = (float)hy;   poly3d->z[2] = topheight;
				poly3d->x[3] = (float)hx;   poly3d->y[3] = (float)ly;   poly3d->z[3] = topheight;
				poly3d->depth = topdepth;
				poly3d->desc = poly->desc;
				if (thickness != 0)
				{
					poly3d = us_3dgetnextpoly(4);
					if (poly3d == 0) return;
					poly3d->x[0] = (float)lx;   poly3d->y[0] = (float)ly;   poly3d->z[0] = botheight;
					poly3d->x[1] = (float)lx;   poly3d->y[1] = (float)hy;   poly3d->z[1] = botheight;
					poly3d->x[2] = (float)hx;   poly3d->y[2] = (float)hy;   poly3d->z[2] = botheight;
					poly3d->x[3] = (float)hx;   poly3d->y[3] = (float)ly;   poly3d->z[3] = botheight;
					poly3d->depth = botdepth;
					poly3d->desc = poly->desc;

					poly3d = us_3dgetnextpoly(4);
					if (poly3d == 0) return;
					poly3d->x[0] = (float)lx;   poly3d->y[0] = (float)ly;   poly3d->z[0] = topheight;
					poly3d->x[1] = (float)lx;   poly3d->y[1] = (float)hy;   poly3d->z[1] = topheight;
					poly3d->x[2] = (float)lx;   poly3d->y[2] = (float)hy;   poly3d->z[2] = botheight;
					poly3d->x[3] = (float)lx;   poly3d->y[3] = (float)ly;   poly3d->z[3] = botheight;
					poly3d->depth = centerdepth;
					poly3d->desc = poly->desc;

					poly3d = us_3dgetnextpoly(4);
					if (poly3d == 0) return;
					poly3d->x[0] = (float)lx;   poly3d->y[0] = (float)hy;   poly3d->z[0] = topheight;
					poly3d->x[1] = (float)hx;   poly3d->y[1] = (float)hy;   poly3d->z[1] = topheight;
					poly3d->x[2] = (float)hx;   poly3d->y[2] = (float)hy;   poly3d->z[2] = botheight;
					poly3d->x[3] = (float)lx;   poly3d->y[3] = (float)hy;   poly3d->z[3] = botheight;
					poly3d->depth = centerdepth;
					poly3d->desc = poly->desc;

					poly3d = us_3dgetnextpoly(4);
					if (poly3d == 0) return;
					poly3d->x[0] = (float)hx;   poly3d->y[0] = (float)hy;   poly3d->z[0] = topheight;
					poly3d->x[1] = (float)hx;   poly3d->y[1] = (float)ly;   poly3d->z[1] = topheight;
					poly3d->x[2] = (float)hx;   poly3d->y[2] = (float)ly;   poly3d->z[2] = botheight;
					poly3d->x[3] = (float)hx;   poly3d->y[3] = (float)hy;   poly3d->z[3] = botheight;
					poly3d->depth = centerdepth;
					poly3d->desc = poly->desc;

					poly3d = us_3dgetnextpoly(4);
					if (poly3d == 0) return;
					poly3d->x[0] = (float)hx;   poly3d->y[0] = (float)ly;   poly3d->z[0] = topheight;
					poly3d->x[1] = (float)lx;   poly3d->y[1] = (float)ly;   poly3d->z[1] = topheight;
					poly3d->x[2] = (float)lx;   poly3d->y[2] = (float)ly;   poly3d->z[2] = botheight;
					poly3d->x[3] = (float)hx;   poly3d->y[3] = (float)ly;   poly3d->z[3] = botheight;
					poly3d->depth = centerdepth;
					poly3d->desc = poly->desc;
				}
				break;
			}

			/* nonmanhattan polygon: handle it as points */
			poly3d = us_3dgetnextpoly(poly->count);
			if (poly3d == 0) return;
			for(i=0; i<poly->count; i++)
			{
				poly3d->x[i] = (float)poly->xv[i];
				poly3d->y[i] = (float)poly->yv[i];
				poly3d->z[i] = topheight;
			}
			poly3d->depth = topdepth;
			poly3d->desc = poly->desc;
			if (thickness != 0)
			{
				poly3d = us_3dgetnextpoly(poly->count);
				if (poly3d == 0) return;
				for(i=0; i<poly->count; i++)
				{
					poly3d->x[i] = (float)poly->xv[i];
					poly3d->y[i] = (float)poly->yv[i];
					poly3d->z[i] = botheight;
				}
				poly3d->depth = botdepth;
				poly3d->desc = poly->desc;

				for(i=0; i<poly->count; i++)
				{
					if (i == 0) previ = poly->count-1; else previ = i-1;
					poly3d = us_3dgetnextpoly(4);
					if (poly3d == 0) return;
					poly3d->x[0] = (float)poly->xv[previ];   poly3d->y[0] = (float)poly->yv[previ];   poly3d->z[0] = topheight;
					poly3d->x[1] = (float)poly->xv[i];       poly3d->y[1] = (float)poly->yv[i];       poly3d->z[1] = topheight;
					poly3d->x[2] = (float)poly->xv[i];       poly3d->y[2] = (float)poly->yv[i];       poly3d->z[2] = botheight;
					poly3d->x[3] = (float)poly->xv[previ];   poly3d->y[3] = (float)poly->yv[previ];   poly3d->z[3] = botheight;
					poly3d->depth = centerdepth;
					poly3d->desc = poly->desc;
				}
			}
			break;
	}
}

POLY3D *us_3dgetnextpoly(INTBIG count)
{
	REGISTER INTBIG newtotal, i;
	REGISTER POLY3D **newlist, *poly3d;

	/* make sure there is room for another 3D polygon */
	if (us_3dpolycount >= us_3dpolytotal)
	{
		newtotal = us_3dpolytotal * 2;
		if (newtotal <= 0) newtotal = 50;
		if (us_3dpolycount > newtotal) newtotal = us_3dpolycount;
		newlist = (POLY3D **)emalloc(newtotal * (sizeof (POLY3D *)), us_tool->cluster);
		if (newlist == 0) return(0);
		for(i=0; i<us_3dpolytotal; i++)
			newlist[i] = us_3dpolylist[i];
		for(i=us_3dpolytotal; i<newtotal; i++)
		{
			newlist[i] = (POLY3D *)emalloc(sizeof (POLY3D), us_tool->cluster);
			if (newlist[i] == 0) return(0);
			newlist[i]->total = 0;
		}
		if (us_3dpolytotal > 0) efree((char *)us_3dpolylist);
		us_3dpolylist = newlist;
		us_3dpolytotal = newtotal;
	}
	poly3d = us_3dpolylist[us_3dpolycount++];
	if (poly3d->total < count)
	{
		if (poly3d->total > 0)
		{
			efree((char *)poly3d->x);
			efree((char *)poly3d->y);
			efree((char *)poly3d->z);
		}
		poly3d->total = 0;
		poly3d->x = (float *)emalloc(count * (sizeof (float)), us_tool->cluster);
		if (poly3d->x == 0) return(0);
		poly3d->y = (float *)emalloc(count * (sizeof (float)), us_tool->cluster);
		if (poly3d->y == 0) return(0);
		poly3d->z = (float *)emalloc(count * (sizeof (float)), us_tool->cluster);
		if (poly3d->z == 0) return(0);
		poly3d->total = count;
	}
	poly3d->count = count;
	return(poly3d);
}
