/*
 * Electric(tm) VLSI Design System
 *
 * File: drcbatch.c
 * Batch design-rule check aid
 * Written by: Steven M. Rubin
 * Inspired by: Mark Moraes, University of Toronto
 *
 * 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 "config.h"
#if DRCAID

#include "global.h"
#include "efunction.h"
#include "drc.h"
#include "database.h"
#include "tech.h"
#include "egraphics.h"
#include "usr.h"

/*********** HASH TABLE ***********/

/*
 * Initial sizes for various hashtables.  This is chosen so it will not
 * have to grow the hashtable for the common case, only for cases with a
 * lot more entries than normal
 */
#define HTABLE_NODEARC     71
#define HTABLE_PORT       113

#define HASH_FULL          -2
#define HASH_EXISTS        -1

#define NOHASHREC ((HASHREC *)-1)

typedef struct
{
	char *key;
	char *datum;
} HASHREC;

#define NOHASHTABLE ((HASHTABLE *)-1)

typedef struct
{
	HASHREC *table;
	INTBIG size;
	INTBIG probe;
} HASHTABLE;

/*
 * The four Hash functions consider key to be an arbitrary pointer --
 * the hash function is simply a modulo of the hash table size.
 * drcb_hashinsert will cause the table to grow if it isn't large enough.
 */
static HASHTABLE  *drcb_freehashtables = NOHASHTABLE;
static HASHREC    *drcb_freehashrecni = NOHASHREC;
static HASHREC    *drcb_freehashrecpp = NOHASHREC;

static HASHTABLE  *drcb_incrementalhpp = NOHASHTABLE;
static HASHTABLE  *drcb_incrementalhai = NOHASHTABLE;
static HASHTABLE  *drcb_incrementalhni = NOHASHTABLE;

/*********** POLYGONS AND SHAPES ***********/

typedef struct
{
	POLYGON **polygons;			/* polygons to be checked on node or arc */
	INTBIG    polylisttotal;	/* size of list */
} POLYLIST;

static POLYGON *drcb_freepolygons = NOPOLYGON;

#define NOSHAPE ((SHAPE *)-1)

/*
 * temp1 in a NODEPROTO is non-zero if the proto has been checked, zero
 * if it has not been checked yet. temp1 in the intersect lists (stored
 * in a dummy NODEPROTO structure) has a count of the number of
 * intersected elements.
 */
typedef struct Ishape
{
	INTSML layer;
	INTSML entrytype;
	union
	{
		NODEINST *ni;
		ARCINST  *ai;
	} entryaddr;
	INTBIG     net;
	HASHTABLE *hpp;
	XARRAY     trans;
	POLYGON   *poly;
} SHAPE;

static SHAPE *drcb_freeshapes = NOSHAPE;

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

#define SPACINGERROR  1
#define MINWIDTHERROR 2
#define NOTCHERROR    3

#define NONET ((INTBIG)-1)

/*
 * The STATE structure contains all the global/static data that used to
 * be scattered around in the DRC procedures. We need one STATE per
 * processor, and all procedures with static writable data of any form
 * must get it from here, so they must be passed this structure. The
 * ONLY safe alternative to this is to lock on every access to those
 * structures. Multiprocessors are such fun...
 */
typedef struct
{
	/* used by checkarcinst, checknodeinst, drcb_getnodeinstshapes, drcb_getarcinstshapes */
	POLYLIST *polylist;

	/* used by badbox, which is called by checkarcinst, checknodeinst, drcb_checkshape */
	POLYLIST *subpolylist;

	/*
	 * cropnodeinst and croparcinst can share one polylist, because they
	 * don't call each other.  These two are called by checkdist, which
	 * is called by badbox.
	 */
	POLYLIST  *croppolylist;
	NODEINST  *tinynodeinst;
	GEOM      *tinyobj;
	INTBIG     netindex;
	HASHTABLE *hni, *hai;
	HASHTABLE *nhpp;
} STATE;

typedef struct
{
	STATE     *state;
	NODEINST  *ni;
	XARRAY     vtrans;
	XARRAY     dtrans;
	NODEPROTO *intersectlist;
} CHECKSHAPEARG;


static STATE      *drcb_state = 0;				/* One for each processor */
static INTSML      drcb_debugflag = 0;
static CELL       *drcb_cell = NOCELL;
static NODEPROTO  *drcb_topfacet = NONODEPROTO;
static NODEPROTO  *drcb_topfacetalways = NONODEPROTO;
static INTBIG      drcb_interactiondistance;	/* maximum DRC interaction distance */
static INTSML      drcb_hierarchicalcheck;

/*********** prototypes for local routines ***********/
static INTSML     drcb_activeontransistor(CHECKSHAPEARG*, GEOM*, INTSML, INTBIG, POLYGON*,
					GEOM*, INTSML, INTBIG, POLYGON*, STATE*, TECHNOLOGY*);
static HASHREC   *drcb_allochashrec(INTBIG size);
static HASHTABLE *drcb_allochashtable(void);
static POLYGON   *drcb_allocpolygon(INTSML count);
static SHAPE     *drcb_allocshape(void);
static INTSML     drcb_badbox(CHECKSHAPEARG*, STATE*, GEOM*, INTSML, TECHNOLOGY*, NODEPROTO*, POLYGON*,
					INTBIG, INTSML);
static void       drcb_box(NODEINST*, INTBIG*, INTBIG*, INTBIG*, INTBIG*);
static INTSML     drcb_boxesintersect(INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG);
static INTSML     drcb_checkallprotocontents(STATE*, NODEPROTO*);
static INTSML     drcb_checkarcinst(STATE*, ARCINST*, INTSML);
static INTSML     drcb_checkdist(CHECKSHAPEARG*, STATE*, TECHNOLOGY*, 
					INTSML, INTBIG, GEOM*, POLYGON*, HASHTABLE*,
					INTSML, INTBIG, GEOM*, POLYGON*, HASHTABLE*,
					INTSML, INTBIG, NODEPROTO*);
static INTSML     drcb_checkintersections(STATE*, NODEINST*, NODEPROTO*, HASHTABLE*, HASHTABLE*, XARRAY);
static INTSML     drcb_checkminwidth(GEOM *geom, INTSML layer, POLYGON *poly, TECHNOLOGY *tech,
					INTSML withinfacet);
static INTSML     drcb_checknodeinst(STATE*, NODEINST*, HASHTABLE*, INTSML);
static INTSML     drcb_checknodeinteractions(STATE*, NODEINST*, XARRAY, HASHTABLE*, HASHTABLE*, NODEPROTO*);
static INTSML     drcb_checkprotocontents(STATE*, NODEPROTO*, HASHTABLE*, HASHTABLE*);
static INTSML     drcb_checkshape(GEOM*, CHECKSHAPEARG*);
static INTSML     drcb_croparcinst(STATE*, ARCINST*, INTSML, INTBIG*, INTBIG*, INTBIG*, INTBIG*);
static INTSML     drcb_cropnodeagainsarcs(STATE *state, NODEINST *ni, INTSML nlayer,
					INTBIG *lx, INTBIG *hx, INTBIG *ly, INTBIG *hy);
static INTSML     drcb_cropnodeinst(CHECKSHAPEARG*, STATE*, NODEINST*, XARRAY, HASHTABLE*, INTBIG,
					INTSML, INTBIG, GEOM*, INTBIG*, INTBIG*, INTBIG*, INTBIG *);
static INTSML     drcb_ensurepolylist(POLYLIST*, INTBIG);
static HASHREC   *drcb_findhashrec(HASHTABLE*, char*);
static NODEPROTO *drcb_findintersectingelements(STATE*, NODEINST*, INTBIG, INTBIG, INTBIG,
					INTBIG, XARRAY, HASHTABLE*, HASHTABLE*, NODEPROTO*);
static INTSML     drcb_findinterveningpoints(POLYGON*, POLYGON*, INTBIG*, INTBIG*, INTBIG*, INTBIG*);
static void       drcb_flatprop2(ARCINST*, HASHTABLE*, char*);
static void       drcb_freehashrec(HASHREC *hr, INTBIG size);
static void       drcb_freehashtable(HASHTABLE *ht);
static char      *drcb_freehpp(char*, char*, char*);
static void       drcb_freeintersectingelements(NODEPROTO*);
static void       drcb_freepolygon(POLYGON *poly);
static void       drcb_freepolylist(POLYLIST *list);
static void       drcb_freertree(RTNODE*);
static void       drcb_freeshape(SHAPE *shape);
static INTSML     drcb_getarcinstshapes(STATE*, NODEPROTO*, ARCINST*, XARRAY, INTBIG, INTBIG,
					INTBIG, INTBIG, HASHTABLE*);
static HASHTABLE *drcb_getarcnetworks(NODEPROTO*, HASHTABLE*, INTBIG*);
static INTBIG     drcb_getarcpolys(ARCINST*, POLYLIST*);
static HASHTABLE *drcb_getinitnets(NODEPROTO*, INTBIG*);
static INTBIG     drcb_getnodeEpolys(NODEINST*, POLYLIST*);
static INTSML     drcb_getnodeinstshapes(STATE*, NODEPROTO*, NODEINST*, XARRAY, INTBIG, INTBIG,
					INTBIG, INTBIG, HASHTABLE*);
static void       drcb_getnodenetworks(NODEINST*, HASHTABLE*, HASHTABLE*, HASHTABLE*, INTBIG*);
static INTBIG     drcb_getnodepolys(NODEINST*, POLYLIST*);
static char      *drcb_growhook(char*, char*, char*);
static INTSML     drcb_halfcropbox(INTBIG *lx, INTBIG *hx, INTBIG *ly, INTBIG *hy,
					INTBIG bx, INTBIG ux, INTBIG by, INTBIG uy);
static void       drcb_hashclear(HASHTABLE*);
static HASHTABLE *drcb_hashcopy(HASHTABLE*);
static HASHTABLE *drcb_hashcreate(INTSML);
static char      *drcb_hashdelete(HASHTABLE*, char*, char**);
static void       drcb_hashdestroy(HASHTABLE*);
static INTSML     drcb_hashgrow(HASHTABLE*, INTSML);
static INTBIG     drcb_hashindex(char*);
static INTBIG     drcb_hashinsert(HASHTABLE*, char*, char*, INTBIG);
static char      *drcb_hashsearch (HASHTABLE*, char*);
static char      *drcb_hashwalk(HASHTABLE*, char*(*)(char*, char*, char*), char*);
static void       drcb_highlighterror(POLYGON *poly1, POLYGON *poly2, NODEPROTO *facet);
static INTSML     drcb_init(NODEPROTO*);
static INTSML     drcb_initonce(void);
static INTSML     drcb_intersectsubtree(STATE*, NODEPROTO*, NODEPROTO*, XARRAY, XARRAY, INTBIG, INTBIG,
					INTBIG, INTBIG, HASHTABLE*);
static INTSML     drcb_isprime(INTBIG);
static void       drcb_linkgeom(GEOM*, NODEPROTO*);
static INTSML     drcb_lookforlayer(NODEPROTO *facet, INTBIG layer, XARRAY moretrans,
					INTBIG lx, INTBIG hx, INTBIG ly, INTBIG hy, INTBIG xf1, INTBIG yf1, INTSML *p1found,
					INTBIG xf2, INTBIG yf2, INTSML *p2found, INTBIG xf3, INTBIG yf3, INTSML *p3found);
static INTSML     drcb_lookforpoints(GEOM *geom1, GEOM *geom2, INTBIG layer, NODEPROTO *facet,
					CHECKSHAPEARG *csap, INTBIG xf1, INTBIG yf1, INTBIG xf2, INTBIG yf2);
static INTBIG     drcb_network(HASHTABLE*, char*, INTBIG);
static INTSML     drcb_objtouch(GEOM*, GEOM*);
static INTBIG     drcb_pickprime(INTBIG);
static char      *drcb_prninettable(char*, char*, char*);
static void       drcb_reporterror(INTBIG, TECHNOLOGY*, char*, INTSML, INTBIG, INTBIG,
					POLYGON*, GEOM*, INTSML, INTBIG, POLYGON*, GEOM*, INTSML, INTBIG);
static INTSML     drcb_walkrtree(RTNODE*, INTSML(*)(GEOM*, CHECKSHAPEARG*), CHECKSHAPEARG*);

/************************ DRC CONTROL ************************/

INTSML drcb_initonce(void)
{
	/* fake cell - used because rtree's use nodeproto->cell->cluster for allocation */
	if (drcb_cell == NOCELL)
	{
		drcb_cell = alloccell(dr_aid->cluster);
		if (drcb_cell == NOCELL) return(1);
		drcb_cell->cellname = "DRC-TEMP";
	}

	if (drcb_state == 0)
	{
		drcb_state = (STATE *)emalloc(sizeof(STATE), dr_aid->cluster);
		if (drcb_state == 0) return(1);
		drcb_state->polylist = (POLYLIST *)emalloc(sizeof(POLYLIST), dr_aid->cluster);
		if (drcb_state->polylist == 0) return(1);

		drcb_state->subpolylist = (POLYLIST *)emalloc(sizeof(POLYLIST), dr_aid->cluster);
		if (drcb_state->subpolylist == 0)
		{
			efree((char *)drcb_state->polylist);
			return(1);
		}

		drcb_state->croppolylist = (POLYLIST *)emalloc(sizeof(POLYLIST),dr_aid->cluster);
		if (drcb_state->croppolylist == 0)
		{
			efree((char *)drcb_state->polylist);
			efree((char *)drcb_state->subpolylist);
			return(1);
		}
		drcb_state->polylist->polylisttotal = 0;
		drcb_state->subpolylist->polylisttotal = 0;
		drcb_state->croppolylist->polylisttotal = 0;
	}
	return(0);
}

void drcb_term(void)
{
	REGISTER POLYGON *poly;
	REGISTER HASHREC *hr;
	REGISTER HASHTABLE *ht;
	REGISTER SHAPE *shape;

	if (drcb_state != 0)
	{
		drcb_freepolylist(drcb_state->polylist);
		drcb_freepolylist(drcb_state->subpolylist);
		drcb_freepolylist(drcb_state->croppolylist);
		efree((char *)drcb_state);
	}
	if (drcb_cell != NOCELL) efree((char *)drcb_cell);

	while (drcb_freepolygons != NOPOLYGON)
	{
		poly = drcb_freepolygons;
		drcb_freepolygons = drcb_freepolygons->nextpolygon;
		freepolygon(poly);
	}

	while (drcb_freeshapes != NOSHAPE)
	{
		shape = drcb_freeshapes;
		drcb_freeshapes = (SHAPE *)drcb_freeshapes->hpp;
		efree((char *)shape);
	}

	if (drcb_incrementalhpp != NOHASHTABLE) drcb_hashdestroy(drcb_incrementalhpp);
	if (drcb_incrementalhai != NOHASHTABLE) drcb_hashdestroy(drcb_incrementalhai);
	if (drcb_incrementalhni != NOHASHTABLE)
	{
		drcb_hashwalk(drcb_incrementalhni, drcb_freehpp, 0);
		drcb_hashdestroy(drcb_incrementalhni);
	}
	while (drcb_freehashrecni != NOHASHREC)
	{
		hr = drcb_freehashrecni;
		drcb_freehashrecni = (HASHREC *)drcb_freehashrecni[0].key;
		efree((char *)hr);
	}
	while (drcb_freehashrecpp != NOHASHREC)
	{
		hr = drcb_freehashrecpp;
		drcb_freehashrecpp = (HASHREC *)drcb_freehashrecpp[0].key;
		efree((char *)hr);
	}
	while (drcb_freehashtables != NOHASHTABLE)
	{
		ht = drcb_freehashtables;
		drcb_freehashtables = (HASHTABLE *)drcb_freehashtables->table;
		efree((char *)ht);
	}
}

/*
 * Initialization of checking for facet "facet".
 */
INTSML drcb_init(NODEPROTO *facet)
{
	REGISTER TECHNOLOGY *tech;
	REGISTER INTSML i;
	REGISTER INTBIG l;

	/* do one-time allocation */
	if (drcb_initonce() != 0) return(1);

	/* determine technology to use */
	tech = (facet != NONODEPROTO) ? facet->tech : el_curtech;

	/* determine maximum DRC interaction distance */
	drcb_interactiondistance = 0;
	for(i = 0; i < tech->layercount; i++)
	{
		l = maxdrcsurround(tech, facet->cell->lib, i);
		if (l > drcb_interactiondistance) drcb_interactiondistance = l;
	}
	if (drcb_debugflag)
		ttyputmsg(_("Maximum interaction distance for technology %s is %s"),
			tech->techname, latoa(drcb_interactiondistance));

	/* clear errors */
	initerrorlogging(_("DRC"));

	/* mark that this will be a hierarchical check */
	drcb_hierarchicalcheck = 1;

	return(0);
}

INTSML drcb_initincrementalcheck(NODEPROTO *facet)
{
	REGISTER NODEINST *ni;
	REGISTER HASHTABLE *h;

	/* do one-time allocation */
	if (drcb_initonce() != 0) return(1);

	/* delete previous hashtables */
	if (drcb_incrementalhpp != NOHASHTABLE) drcb_hashdestroy(drcb_incrementalhpp);
	if (drcb_incrementalhai != NOHASHTABLE) drcb_hashdestroy(drcb_incrementalhai);
	if (drcb_incrementalhni != NOHASHTABLE)
	{
		drcb_hashwalk(drcb_incrementalhni, drcb_freehpp, 0);
		drcb_hashdestroy(drcb_incrementalhni);
	}
	drcb_state->hni = NULL;
	drcb_state->hai = NULL;

	/* initialize tables for this facet */
	drcb_state->netindex = 1;
	drcb_topfacetalways = drcb_topfacet = facet;
	drcb_incrementalhpp = drcb_getinitnets(facet, &drcb_state->netindex);
	if (drcb_incrementalhpp == 0) return(1);

	drcb_incrementalhai = drcb_getarcnetworks(facet, drcb_incrementalhpp, &drcb_state->netindex);
	if (drcb_incrementalhai == 0) return(1);

	drcb_incrementalhni = drcb_hashcreate(HTABLE_NODEARC);
	if (drcb_incrementalhni == 0) return(1);

	/* build network tables for all objects */
	for(ni = facet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex != 0)
		{
			h = drcb_hashcreate(HTABLE_PORT);
			if (h == 0) return(1);
			drcb_getnodenetworks(ni, drcb_incrementalhpp, drcb_incrementalhai,
				h, &drcb_state->netindex);
			if (drcb_hashinsert(drcb_incrementalhni, (char *)ni, (char *)h, 0) != 0)
			{
				ttyputerr(_("DRC error 1 inserting node %s into hash table"),
					describenodeinst(ni));
				return(1);
			}
		}
	}
	drcb_state->hni = drcb_incrementalhni;
	drcb_state->hai = drcb_incrementalhai;

	/* mark that this will be a nonhierarchical check */
	drcb_hierarchicalcheck = 0;
	return(0);
}

INTSML drcb_checkincremental(GEOM *geom, INTSML partial)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER HASHTABLE *h;

	if (geom->entrytype == OBJNODEINST)
	{
		ni = geom->entryaddr.ni;
		if (ni->parent != drcb_topfacet) return(0);
		if (ni->proto->primindex == 0) return(0);
		h = (HASHTABLE *)drcb_hashsearch(drcb_incrementalhni, (char *)ni);
		if (h == NULL)
		{
			ttyputerr(_("DRC error 2 locating node %s in hash table"),
				describenodeinst(ni));
			return(0);
		}
		drcb_state->nhpp = h;
		(void)drcb_checknodeinst(drcb_state, ni, h, partial);
	} else
	{
		ai = geom->entryaddr.ai;
		if (ai->parent != drcb_topfacet) return(0);
		drcb_state->nhpp = NULL;
		(void)drcb_checkarcinst(drcb_state, ai, partial);
	}
	return(0);
}

/*
 * Main entry point - initializes all variables, caches some
 * information, and starts the drc.  Invoked by "tellaid drc hierarchical run"
 * to run hierarchically on the current node.  Returns number of errors found.
 */
INTBIG drcb_check(NODEPROTO *facet)
{
	REGISTER NODEPROTO *np;
	REGISTER LIBRARY *lib;

	if (facet == NONODEPROTO) return(0);
	if (drcb_init(facet) != 0) return(0);

	/* mark all facets unchecked */
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
		for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			np->temp1 = 0;

	/* allocate parcel of net number space to each processor */
	drcb_state->netindex = 1;

	/* remember the top of the tree for error display purposes */
	drcb_topfacetalways = drcb_topfacet = facet;

	/* recursively check this and all lower facets */
	if (drcb_checkallprotocontents(drcb_state, facet) > 0) return(0);

	/* sort the errors by layer */
	sorterrors();
	termerrorlogging();
	return(numerrors());
}

/*
 * Toggles debugging on and off. Invoked as "tellaid drc hierarchical verbose"
 */
INTSML drcb_debug(void)
{
	drcb_debugflag = 1 - drcb_debugflag;
	ttyputmsg(_("Batch DRC tracing turned %s"),
		(drcb_debugflag == 0 ? _("off") : _("on")));
	return(0);
}

/*
 * Turns off all saved date information about valid DRC.
 */
void drcb_reset_dates(void)
{
	REGISTER LIBRARY *lib;
	REGISTER NODEPROTO *np;
	REGISTER VARIABLE *var;

	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
	{
		for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
		{
			var = getvalkey((INTBIG)np, VNODEPROTO, VINTEGER, dr_lastgooddrckey);
			if (var == NOVARIABLE) continue;
			delvalkey((INTBIG)np, VNODEPROTO, dr_lastgooddrckey);
		}
	}
}

/************************ CONTENTS CHECKING ************************/

/*
 * recursively walks through a facet, checking all
 * instances and sub-instances.  Returns positive on error,
 * negative if the facet didn't need to be checked.
 */
INTSML drcb_checkallprotocontents(STATE *state, NODEPROTO *np)
{
	REGISTER HASHTABLE *nhpp, *nhai;
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *savetop;
	REGISTER VARIABLE *var;
	float elapsed;
	REGISTER INTSML ret, allsubfacetsstillok;
	REGISTER INTBIG errcount;
	REGISTER UINTBIG lastgooddate, lastchangedate;
	NODEINST top;	/* Dummy node instance for node */
	GEOM topgeom;	/* Dummy geometry module for dummy top node */

	/* remember how many errors there are on entry */
	errcount = numerrors();

	/* first check all subfacets */
	allsubfacetsstillok = 1;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex == 0 && ni->proto->temp1 == 0)
		{
			/* recursively check the subfacet */
			savetop = drcb_topfacet;
			drcb_topfacet = ni->proto;
			ret = drcb_checkallprotocontents(state, ni->proto);
			drcb_topfacet = savetop;
			if (ret > 0) return(1);
			if (ret == 0) allsubfacetsstillok = 0;
		}
	}

	/* prepare to check facet */
	np->temp1++;

	/* if the facet hasn't changed since the last good check, stop now */
	if (allsubfacetsstillok != 0)
	{
		var = getvalkey((INTBIG)np, VNODEPROTO, VINTEGER, dr_lastgooddrckey);
		if (var != NOVARIABLE)
		{
			lastgooddate = (UINTBIG)var->addr;
			lastchangedate = np->revisiondate;
			if (lastchangedate <= lastgooddate) return(-1);
		}
	}

	/* announce progress */
	ttyputmsg(_("Checking facet %s"), describenodeproto(np));

	/* get information about this facet */
	state->netindex = 1;    /* !! per-processor initialization */
	nhpp = drcb_getinitnets(np, &state->netindex);
	if (nhpp == 0) return(1);
	nhai = drcb_getarcnetworks(np, nhpp, &state->netindex);
	if (nhai == 0) return(1);

	/* check the facet contents nonhierarchically */
	if (drcb_checkprotocontents(state, np, nhpp, nhai) != 0) return(1);

	/*
	 * Create dummy node instance for top level cell. It has the same
	 * coordinates as the nodeproto, and no parent. Those are the only
	 * relevant members for it.
	 */
	top = *dummynode();
	topgeom.firstvar = NOVARIABLE;
	topgeom.numvar = 0;
	topgeom.entrytype = OBJNODEINST;
	topgeom.entryaddr.ni = &top;

	top.lowx = topgeom.lowx = np->lowx;
	top.highx = topgeom.highx = np->highx;
	top.lowy = topgeom.lowy = np->lowy;
	top.highy = topgeom.highy = np->highy;
	top.proto = np;
	top.geom = &topgeom;

	/* check interactions related to this facet (hierarchically) */
	ret = drcb_checknodeinteractions(state, &top, el_matid, nhpp, nhai, NONODEPROTO);
	if (ret > 0) return(1);

	/* stop now if interrupted */
	if (stopping(STOPREASONDRC)) return(0);

	/* if there were no errors, remember that */
	elapsed = endtimer();
	if (numerrors() == errcount)
	{
		(void)setvalkey((INTBIG)np, VNODEPROTO, dr_lastgooddrckey,
			(INTBIG)getcurrenttime(), VINTEGER);
		ttyputmsg(_("   No errors found (%g seconds so far)"), elapsed);
	} else
	{
		ttyputmsg(_("   Errors found (%g seconds so far)"), elapsed);
	}

	return(0);
}

/*
 * Actual check of a node prototype - goes through all primitive
 * nodeinsts and all arcinsts in a nodeproto and checks that they are
 * design rule correct within the cell.  Any errors are caused by excess
 * paint are reported.  Any errors caused by the absence of paint are
 * possibly spurious -- they're stored in case a subcell gets rid of
 * them.  Even if there are no subcells within interacting distance of an
 * "absence of paint" error, it can be spurious if it gets removed by
 * some overlapping instance. !! If it isn't removed in any instance of
 * the cell, then it should be reported as being a proto error, if it is
 * removed in some instances but not in others, then it should be
 * reported as an instance error.  How do we tell?
 * Returns nonzero on error.
 */
static INTSML drcb_checkprotocontents(STATE *state, NODEPROTO *np, HASHTABLE *hpp, HASHTABLE *hai)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER INTSML freq, wanttostop, ret;
	REGISTER HASHTABLE *hni, *nhpp, *h;

	hni = drcb_hashcreate(HTABLE_NODEARC);
	if (hni == 0) return(1);

	/* build network tables for all objects */
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex != 0)
		{
			nhpp = drcb_hashcreate(HTABLE_PORT);
			if (nhpp == 0) return(1);
			drcb_getnodenetworks(ni, hpp, hai, nhpp, &state->netindex);
			if (drcb_hashinsert(hni, (char *)ni, (char *)nhpp, 0) != 0)
			{
				ttyputerr(_("DRC error 3 inserting node %s into hash table"),
					describenodeinst(ni));
			}
		}
	}
	state->hni = hni;
	state->hai = hai;

	/* now check every primitive node object */
	freq = 0;
	wanttostop = 0;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (((++freq)%50) == 0)
		{
			if (stopping(STOPREASONDRC)) break;
		}
		if (ni->proto->primindex != 0)
		{
			h = (HASHTABLE *)drcb_hashsearch(hni, (char *)ni);
			if (h == NULL)
			{
				ttyputerr(_("DRC error 4 locating node %s in hash table"),
					describenodeinst(ni));
			}
			state->nhpp = h;
			ret = drcb_checknodeinst(state, ni, h, 1);
			if (ret != 0 && (dr_options&DRCFIRSTERROR) != 0) { wanttostop = 1;   break; }
			if (el_pleasestop != 0) { wanttostop = 1;   break; }
		}
	}

	/* check all arcs */
	if (wanttostop == 0)
	{
		state->nhpp = NULL;
		for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
		{
			if (((++freq)%50) == 0)
				if (stopping(STOPREASONDRC)) break;
			ret = drcb_checkarcinst(state, ai, 1);
			if (ret != 0 && (dr_options&DRCFIRSTERROR) != 0) break;
			if (el_pleasestop != 0) break;
		}
	}

	/* clean up */
	drcb_hashwalk(hni, drcb_freehpp, 0);
	drcb_hashdestroy(hni);
	state->hni = NULL;
	state->hai = NULL;
	return(0);
}

/*
 * routine to check the design rules about nodeinst "ni".  Only check those
 * other objects whose geom pointers are greater than this one (i.e. only
 * check any pair of objects once).
 * Returns nonzero if an error was found.
 */
static INTSML drcb_checknodeinst(STATE *state, NODEINST *ni, HASHTABLE *nhpp, INTSML partial)
{
	REGISTER INTBIG tot, j;
	REGISTER INTSML ret, errorsfound;
	XARRAY trans;
	REGISTER INTBIG net;
	REGISTER POLYLIST *polylist;
	REGISTER POLYGON *poly;

	makerot(ni, trans);

	/* get all of the polygons on this node */
	polylist = state->polylist;
	tot = drcb_getnodeEpolys(ni, polylist);

	/* examine the polygons on this node */
	errorsfound = 0;
	for(j=0; j<tot; j++)
	{
		poly = polylist->polygons[j];
		if (poly->layer < 0) continue;
		if (poly->portproto == NOPORTPROTO) net = NONET; else
			net = drcb_network(nhpp, (char *)poly->portproto, 1);
		xformpoly(poly, trans);
		ret = drcb_badbox(0, state, ni->geom, poly->layer,
			ni->proto->tech, ni->parent, poly, net, partial);
		if (ret != 0)
		{
			errorsfound = 1;
			if ((dr_options&DRCFIRSTERROR) != 0) break;
		}
		ret = drcb_checkminwidth(ni->geom, poly->layer, poly, ni->proto->tech, 1);
		if (ret != 0)
		{
			errorsfound = 1;
			if ((dr_options&DRCFIRSTERROR) != 0) break;
		}
		if (el_pleasestop != 0) break;
	}
	return(errorsfound);
}

/*
 * routine to check the design rules about arcinst "ai".
 * Returns nonzero if errors were found.
 */
static INTSML drcb_checkarcinst(STATE *state, ARCINST *ai, INTSML partial)
{
	REGISTER INTBIG tot, j;
	REGISTER INTSML ret, errorsfound;
	REGISTER INTBIG net;
	REGISTER POLYLIST *polylist;
	REGISTER POLYGON *poly;

	/* get all of the polygons on this arc */
	polylist = state->polylist;
	tot = drcb_getarcpolys(ai, polylist);

	/* examine the polygons on this arc */
	errorsfound = 0;
	for(j=0; j<tot; j++)
	{
		poly = polylist->polygons[j];
		if (poly->layer < 0) continue;
		net = drcb_network(state->hai, (char *)ai, 0);
		ret = drcb_badbox(0, state, ai->geom, poly->layer,
			ai->proto->tech, ai->parent, poly, net, partial);
		if (ret != 0)
		{
			errorsfound = 1;
			if ((dr_options&DRCFIRSTERROR) != 0) break;
		}
		ret = drcb_checkminwidth(ai->geom, poly->layer, poly, ai->proto->tech, 1);
		if (ret != 0)
		{
			errorsfound = 1;
			if ((dr_options&DRCFIRSTERROR) != 0) break;
		}
		if (el_pleasestop != 0) break;
	}
	return(errorsfound);
}

/*
 * Routine to ensure that polygon "poly" on layer "layer" from object "geom" in
 * technology "tech" meets minimum width rules.  If it is too narrow, other layers
 * in the vicinity are checked to be sure it is indeed an error.  Returns nonzero
 * if an error is found.
 */
INTSML drcb_checkminwidth(GEOM *geom, INTSML layer, POLYGON *poly, TECHNOLOGY *tech,
	INTSML withinfacet)
{
	REGISTER INTBIG minwidth, actual;
	INTBIG lx, hx, ly, hy, ix, iy;
	INTSML p1found, p2found, p3found;
	REGISTER INTSML ang, oang, perpang;
	REGISTER NODEPROTO *facet;
	REGISTER INTBIG xl1, yl1, xl2, yl2, xl3, yl3, xr1, yr1, xr2, yr2, xr3, yr3,
		cx, cy, fx, fy, tx, ty, ofx, ofy, otx, oty, i, j;

	facet = geomparent(geom);
	minwidth = drcminwidth(tech, facet->cell->lib, layer);
	if (minwidth < 0) return(0);

	/* simpler analysis if manhattan */
	if (isbox(poly, &lx, &hx, &ly, &hy) != 0)
	{
		if (hx - lx >= minwidth && hy - ly >= minwidth) return(0);
		if (hx - lx < minwidth)
		{
			actual = hx - lx;
			xl1 = xl2 = xl3 = lx - 1;
			xr1 = xr2 = xr3 = hx + 1;
			yl1 = yr1 = ly;
			yl2 = yr2 = hy;
			yl3 = yr3 = (yl1+yl2)/2;
		} else
		{
			actual = hy - ly;
			xl1 = xr1 = lx;
			xl2 = xr2 = hx;
			xl3 = xr3 = (xl1+xl2)/2;
			yl1 = yl2 = yl3 = ly - 1;
			yr1 = yr2 = yr3 = hy + 1;
		}

		/* see if there is more of this layer adjoining on either side */
		p1found = p2found = p3found = 0;
		if (drcb_lookforlayer(facet, layer, el_matid, lx-1, hx+1, ly-1, hy+1,
			xl1, yl1, &p1found, xl2, yl2, &p2found, xl3, yl3, &p3found) != 0) return(0);

		p1found = p2found = p3found = 0;
		if (drcb_lookforlayer(facet, layer, el_matid, lx-1, hx+1, ly-1, hy+1,
			xr1, yr1, &p1found, xr2, yr2, &p2found, xr3, yr3, &p3found) != 0) return(0);

		drcb_reporterror(MINWIDTHERROR, tech, 0, withinfacet, minwidth, actual,
			poly, geom, layer, NONET, NOPOLYGON, NOGEOM, 0, NONET);
		return(1);
	}

	/* nonmanhattan polygon: stop now if it has no size */
	switch (poly->style)
	{
		case FILLED:
		case CLOSED:
		case CROSSED:
		case OPENED:
		case OPENEDT1:
		case OPENEDT2:
		case OPENEDT3:
		case VECTORS:
			break;
		default:
			return(0);
	}

	/* simple check of nonmanhattan polygon for minimum width */
	getbbox(poly, &lx, &hx, &ly, &hy);
	actual = mini(hx-lx, hy-ly);
	if (actual < minwidth)
	{
		drcb_reporterror(MINWIDTHERROR, tech, 0, withinfacet, minwidth, actual,
			poly, geom, layer, NONET, NOPOLYGON, NOGEOM, 0, NONET);
		return(1);
	}

	/* check distance of each line's midpoint to perpendicular opposite point */
	for(i=0; i<poly->count; i++)
	{
		if (i == 0)
		{
			fx = poly->xv[poly->count-1];   fy = poly->yv[poly->count-1];
		} else
		{
			fx = poly->xv[i-1];   fy = poly->yv[i-1];
		}
		tx = poly->xv[i];   ty = poly->yv[i];
		if (fx == tx && fy == ty) continue;
		ang = figureangle(fx, fy, tx, ty);
		cx = (fx + tx) / 2;
		cy = (fy + ty) / 2;
		perpang = (ang + 900) % 3600;
		for(j=0; j<poly->count; j++)
		{
			if (j == i) continue;
			if (j == 0)
			{
				ofx = poly->xv[poly->count-1];   ofy = poly->yv[poly->count-1];
			} else
			{
				ofx = poly->xv[j-1];   ofy = poly->yv[j-1];
			}
			otx = poly->xv[j];   oty = poly->yv[j];
			if (ofx == otx && ofy == oty) continue;
			oang = figureangle(ofx, ofy, otx, oty);
			if ((ang%1800) == (oang%1800))
			{
				/* lines are parallel: see if they are colinear */
				if (isonline(fx, fy, tx, ty, ofx, ofy) != 0) continue;
				if (isonline(fx, fy, tx, ty, otx, oty) != 0) continue;
				if (isonline(ofx, ofy, otx, oty, fx, fy) != 0) continue;
				if (isonline(ofx, ofy, otx, oty, tx, ty) != 0) continue;
			}
			if (intersect(cx, cy, perpang, ofx, ofy, oang, &ix, &iy) < 0) continue;
			if (ix < mini(ofx, otx) || ix > maxi(ofx, otx)) continue;
			if (iy < mini(ofy, oty) || iy > maxi(ofy, oty)) continue;
			actual = computedistance(cx, cy, ix, iy);
			if (actual < minwidth)
			{
				/* look between the points to see if it is minimum width or notch */
				if (isinside((cx+ix)/2, (cy+iy)/2, poly) != 0)
				{
					drcb_reporterror(MINWIDTHERROR, tech, 0, withinfacet, minwidth, actual,
						poly, geom, layer, NONET, NOPOLYGON, NOGEOM, 0, NONET);
				} else
				{
					drcb_reporterror(NOTCHERROR, tech, 0, withinfacet, minwidth, actual,
						poly, geom, layer, NONET, poly, geom, layer, NONET);
				}
				return(1);
			}
		}
	}
	return(0);
}

/************************ INTERACTIONS BETWEEN FACETS ************************/

/*
 * Recursive check of a node instance (node prototype + transformation).
 * Returns nonzero on error, zero if no errors found, negative if errors found.
 */
static INTSML drcb_checknodeinteractions(STATE *state, NODEINST *nodeinst, XARRAY mytrans,
	HASHTABLE *hpp, HASHTABLE *hai, NODEPROTO *intersect_list)
{
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *child_intersects;
	XARRAY subtrans, temptrans1, temptrans2;
	INTBIG lx, hx, ly, hy;
	REGISTER INTSML ret, errorsfound;
	REGISTER HASHTABLE *nhai, *nhpp;

	if (drcb_debugflag)
		ttyputmsg(_("Checking contents of facet %s"), describenodeproto(nodeinst->proto));

	errorsfound = 0;
	for(ni = nodeinst->proto->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex != 0) continue;
		if (stopping(STOPREASONDRC)) break;

		/* facet: recursively check it */
		nhpp = drcb_hashcreate(HTABLE_PORT);
		if (nhpp == 0) return(1);

		drcb_getnodenetworks(ni, hpp, hai, nhpp, &state->netindex);
		nhai = drcb_getarcnetworks(ni->proto, nhpp, &state->netindex);
		if (nhai == 0) return(1);

		/*
		 * Compute transformation matrix that maps the contents of the
		 * instance back to the facet that contains the instance.
		 */
		makerot(ni, temptrans1);
		transmult(temptrans1, mytrans, temptrans2);
		maketrans(ni, temptrans1);
		transmult(temptrans1, temptrans2, subtrans);

		/*
		 * drcb_box returns the bounding box of nodeinst - this box is
		 * relative to the parent prototype, which is fine, because that's
		 * what we'll be searching for intersecting elements
		 */
		drcb_box(ni, &lx, &hx, &ly, &hy);

		/*
		 * find intersecting elements returns elements relative to
		 * nodeinst.  all checking of the nodeinst can then be done
		 * in the nodeinst's coordinate system
		 */
		child_intersects = drcb_findintersectingelements(state, ni,
			lx, hx, ly, hy, subtrans, hpp, hai, intersect_list);
		if (drcb_debugflag)
			ttyputmsg(_("  Facet instance %s created pseudofacet %d"),
				describenodeinst(ni), child_intersects);

		/*
		 * Check each intersecting element with any interacting primitive
		 * nodes in the nodeinst.
		 */
		if (child_intersects != NONODEPROTO)
		{
			if (drcb_debugflag)
				ttyputmsg(_("Now checking intersections with pseudo-facet %d"), child_intersects);
			ret = drcb_checkintersections(state, ni, child_intersects, nhpp, nhai, subtrans);
			if (ret > 0) return(1);
			if (ret < 0)
			{
				errorsfound = 1;
				if ((dr_options&DRCFIRSTERROR) != 0) break;
			}
		}

		/* "child_intersects", "nhpp" and "nhai" will be freed inside drcb_checknodeinteractions */
		ret = drcb_checknodeinteractions(state, ni, subtrans, nhpp, nhai, child_intersects);
		if (ret > 0) return(1);
		if (ret < 0)
		{
			errorsfound = 1;
			if ((dr_options&DRCFIRSTERROR) != 0) break;
		}
	}

	drcb_freeintersectingelements(intersect_list);
	drcb_hashdestroy(hpp);
	drcb_hashdestroy(hai);
	if (errorsfound != 0) return(-1);
	return(0);
}

/*
 * Computes the drc box for a node instance - a drc box is the bounding
 * box of the node, extended outward by the maximum drid
 */
static void drcb_box(NODEINST *ni, INTBIG *lxp, INTBIG *hxp, INTBIG *lyp, INTBIG *hyp)
{
	*lxp = ni->geom->lowx - drcb_interactiondistance;
	*lyp = ni->geom->lowy - drcb_interactiondistance;
	*hxp = ni->geom->highx + drcb_interactiondistance;
	*hyp = ni->geom->highy + drcb_interactiondistance;
}

/*
 * Actual check of a node instance - goes through each intersecting
 * element in the instance and makes sure the node elements that
 * intersect with that element are drc correct.  Checks if any spurious
 * errors are removed or confirmed.
 * Returns positive on error, zero if no errors found, negative if errors found.
 */
static INTSML drcb_checkintersections(STATE *state, NODEINST *nip, NODEPROTO *intersect_list,
	HASHTABLE *hpp, HASHTABLE *hai, XARRAY mytrans)
{
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *niproto;
	REGISTER HASHTABLE *hni, *nhpp;
	CHECKSHAPEARG csa;
	REGISTER INTSML ret;

	hni = drcb_hashcreate(HTABLE_NODEARC);
	if (hni == 0) return(1);

	csa.ni = nip;
	csa.state = state;
	state->hni = hni;
	state->hai = hai;

	/* build network tables for all objects in nip's prototype */
	niproto = nip->proto;
	for(ni = niproto->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex != 0)
		{
			nhpp = drcb_hashcreate(HTABLE_PORT);
			if (nhpp == 0) return(1);
			drcb_getnodenetworks(ni, hpp, hai, nhpp, &state->netindex);
			if (drcb_hashinsert(hni, (char *)ni, (char *)nhpp, 0) != 0)
			{
				ttyputerr(_("DRC error 5 inserting node %s into hash table"),
					describenodeinst(ni));
			}
		}
	}

	/*
	 * transformation matrix for visual debugging.  This transforms
	 * the shape from the coordinate space of nip's nodeproto to the
	 * coordinate space of nip's parent, which is what is being
	 * displayed.
	 */
	transcpy(mytrans, csa.vtrans);

	if (drcb_debugflag)
		ttyputmsg(_("Checking pseudo-facet %d (made from facet instance %s)..."), intersect_list, describenodeinst(nip));

	/* check each geom in the intersect list */
	csa.intersectlist = intersect_list;
	ret = drcb_walkrtree(intersect_list->rtree, drcb_checkshape, &csa);

	if (drcb_debugflag)
		ttyputmsg(_("...Done checking pseudo-facet %d"), intersect_list);

	drcb_hashwalk(hni, drcb_freehpp, 0);
	drcb_hashdestroy(hni);
	state->hni = NULL;
	state->hai = NULL;
	if (ret != 0) return(-1);
	return(0);
}

/*
 * Walks an R Tree hierarchy - traversing the tree in a depth first
 * search running the supplied function 'func' on all terminal nodes.
 * This should probably be an Electric database routine. 'func' is called
 * as (*func)(GEOM *geom, CHECKSHAPEARG *arg) where geom is a pointer to the
 * geometry object stored in that R tree node. 'func' may NOT modify the
 * geometry related contents of geom (since that would mean a change in
 * the R tree structure).
 * If the function returns nonzero, this routine returns nonzero.
 */
static INTSML drcb_walkrtree(RTNODE *rtn, INTSML (*func)(GEOM*, CHECKSHAPEARG*), CHECKSHAPEARG *arg)
{
	REGISTER INTSML j, ret, errorsfound;

	errorsfound = 0;
	for(j = 0; j < rtn->total; j++)
	{
		if (rtn->flag != 0)
		{
			ret = (*func)((GEOM *)(rtn->pointers[j]), arg);
		} else
		{
			ret = drcb_walkrtree((RTNODE *)rtn->pointers[j], func, arg);
		}
		if (ret != 0)
		{
			errorsfound = 1;
			if ((dr_options&DRCFIRSTERROR) != 0) break;
		}
		if (el_pleasestop != 0) break;
	}
	return(errorsfound);
}

/*
 * Routine to check a shape in the R-Tree.  Returns nonzero if an error was found.
 */
static INTSML drcb_checkshape(GEOM *g, CHECKSHAPEARG *csap)
{
	REGISTER NODEINST *sni, *ni;
	REGISTER INTSML ret;
	REGISTER ARCINST *sai;
	REGISTER SHAPE *shape;
	static POLYGON *rectpoly = NOPOLYGON;
	REGISTER POLYGON *poly;

	/*
	 * Check shape against all intersecting elements in the nodeinst.
	 * We don't need to transform anything.
	 */
	ni = csap->ni;
	shape = (SHAPE *)g->entryaddr.blind;

	if (shape->poly == NOPOLYGON)
	{
		if (rectpoly == NOPOLYGON) rectpoly = allocstaticpolygon(4, dr_aid->cluster);
		rectpoly->xv[0] = g->lowx;    rectpoly->yv[0] = g->lowy;
		rectpoly->xv[1] = g->highx;   rectpoly->yv[1] = g->lowy;
		rectpoly->xv[2] = g->highx;   rectpoly->yv[2] = g->highy;
		rectpoly->xv[3] = g->lowx;    rectpoly->yv[3] = g->highy;
		rectpoly->count = 4;
		poly = rectpoly;
	} else
	{
		poly = shape->poly;
	}

	transcpy(shape->trans, csap->dtrans);
	if (shape->entrytype == OBJNODEINST)
	{
		sni = shape->entryaddr.ni;
		if (drcb_debugflag)
		{
			ttyputmsg(_("    From Pseudo-facet: node %s, layer %s, net %d"), describenodeinst(sni),
				layername(sni->proto->tech, shape->layer), shape->net);
		}
		csap->state->nhpp = shape->hpp;
		ret = drcb_badbox(csap, csap->state, sni->geom, shape->layer, sni->proto->tech, ni->proto,
			poly, shape->net, 0);
		csap->state->nhpp = NULL;
	} else if (shape->entrytype == OBJARCINST)
	{
		sai = shape->entryaddr.ai;
		if (drcb_debugflag)
			ttyputmsg(_("    From Pseudo-facet: arc %s, layer %s, net %d"), describearcinst(sai),
				layername(sai->proto->tech, shape->layer), shape->net);
		csap->state->nhpp = NULL;
		ret = drcb_badbox(csap, csap->state, sai->geom, shape->layer, sai->proto->tech, ni->proto,
			poly, shape->net, 0);
	}
	return(ret);
}

/*
 * To intersect elements, drcb_findintersectingelements creates a
 * transformation that describes how to go from nodeinst->parent
 * (facet that contains the instance) to nodeinst->proto (prototype of
 * the instance). This is the inverse of the matrix obtained by makerot
 * and maketrans, i.e. use makerotI and maketransI.
 * drcb_findintersectingelements then transforms all intersecting elements
 * it finds by that matrix. For child node instances,
 * drcb_findintersectingelements calls drcb_intersectsubtree on them, giving
 * them a new transformation matrix. This matrix is the product of the
 * transformation to go from the child node instance's prototype to the
 * parent (i.e. the result of makerot/maketrans on the child instance)
 * and the current transformation. Thus we hand the transformation down
 * the hierarchy.
 *
 * We also hand a clip box down to drcb_findintersectingelements.
 * The box is inverse transformed from the coordinate
 * system of the parent to the coordinates of the child instance's
 * prototype, and handed in to the intersect procedure for the prototype
 * along with the matrix above. The child instance's prototype is
 * searched using the transformed clip box -- this may recurse.
 *
 * All transformation matrices and clip boxes that are passed down via
 * recursion are on the stack.
 *
 * !! Try to avoid having drcb_findintersectingelements AND drcb_intersectsubtree
 * even if it means passing in a nodeinst value to each level?
 */

/*
 * Computes the intersecting elements above a node instance, and store
 * it in the instance.
 */
static NODEPROTO *drcb_findintersectingelements(STATE *state, NODEINST *nodeinst,
	INTBIG lowx, INTBIG highx, INTBIG lowy, INTBIG highy, XARRAY mytrans,
	HASHTABLE *hpp, HASHTABLE *hai, NODEPROTO *parentintersectfacet)
{
	REGISTER HASHTABLE *nhpp;
	REGISTER INTBIG searchkey, subnodelistcount, subnodelisttotal;
	REGISTER NODEPROTO *intersect_list;
	REGISTER NODEINST *ni;
	REGISTER GEOM *geom;
	XARRAY itrans, subtrans, temptrans1, temptrans2, temptrans3;
	INTBIG lx, hx, ly, hy;

	nhpp = drcb_hashcreate(HTABLE_PORT);
	if (nhpp == 0) return(NONODEPROTO);
	if (nodeinst->parent == NONODEPROTO) return(NONODEPROTO);

	intersect_list = allocnodeproto(dr_aid->cluster);
	if (intersect_list == NONODEPROTO) return(NONODEPROTO);
	intersect_list->cell = drcb_cell;
	intersect_list->temp1 = 0;	/* stores a count of number of intersections */
	if (geomstructure(intersect_list) != 0)
	{
		freenodeproto(intersect_list);
		return(NONODEPROTO);
	}

	/* compute matrix to transform the shapes into nodeinst's coordinate space */
	makerotI(nodeinst, temptrans1);
	maketransI(nodeinst, temptrans2);
	transmult(temptrans1, temptrans2, itrans);
	if (drcb_debugflag)
		ttyputmsg(_("  Facet instance %s has this around it:"), describenodeinst(nodeinst));

	searchkey = initsearch(lowx, highx, lowy, highy, nodeinst->parent);
	subnodelistcount = subnodelisttotal = 0;
	while((geom = nextobject(searchkey)) != NOGEOM)
	{
		if (geom->entrytype == OBJNODEINST)
		{
			ni = geom->entryaddr.ni;

			/* skip the instance which "called" this */
			if (ni == nodeinst) continue;

			/* Compute networks for the nodeinst */
			if (ni->proto->primindex == 0)
			{
				if (drcb_debugflag)
					ttyputmsg(_("    Sub instance %s:"), describenodeinst(ni));
				drcb_getnodenetworks(ni, hpp, hai, nhpp, &state->netindex);

				/*
				 * transform the intersecting box to the child
				 * nodeproto's coordinate system so we can recurse down
				 * the tree still intersecting with the same box.
				 */
				makerotI(ni, temptrans1);
				maketransI(ni, temptrans2);
				transmult(temptrans1, temptrans2, temptrans3);
				lx = lowx;
				hx = highx;
				ly = lowy;
				hy = highy;
				xformbox(&lx, &hx, &ly, &hy, temptrans3);

				/*
				 * Compute the inverse transform to apply to objects
				 * that intersect so they will get transformed back to
				 * the original nodeinst's coordinate space. Make a new
				 * itrans for ni in subtrans.
				 */
				maketrans(ni, temptrans1);
				makerot(ni, temptrans2);
				transmult(temptrans1, temptrans2, temptrans3);
				transmult(temptrans3, itrans, subtrans);

				/*
				 * mytrans transforms to the displayed prototype's
				 * coordinate system. Make a new mytrans for the ni in
				 * temptrans1 so we can pass it down as we recurse.
				 */
				transmult(temptrans3, mytrans, temptrans1);
				if (drcb_intersectsubtree(state, intersect_list, ni->proto, subtrans,
					temptrans1, lx, hx, ly, hy, nhpp) != 0) return(NONODEPROTO);
				drcb_hashclear(nhpp);
			} else
			{
				if (drcb_debugflag)
				{
					INTBIG bx, ux, by, uy, swap;
					xform(ni->geom->lowx, ni->geom->lowy, &bx, &by, itrans);
					xform(ni->geom->highx, ni->geom->highy, &ux, &uy, itrans);
					if (bx > ux) { swap = bx;  bx = ux;  ux = swap; }
					if (by > uy) { swap = by;  by = uy;  uy = swap; }
					ttyputmsg(_("    Primitive node %s (%s<=X<=%s %s<=Y<=%s inside %s):"), describenodeinst(ni),
						latoa(bx), latoa(ux), latoa(by), latoa(uy), describenodeproto(nodeinst->proto));
				}
				drcb_getnodenetworks(ni, hpp, hai, nhpp, &state->netindex);
				if (drcb_getnodeinstshapes(state, intersect_list, ni, itrans,
					lowx, highx, lowy, highy, nhpp) != 0) return(NONODEPROTO);
				drcb_hashclear(nhpp);
			}
		} else if (geom->entrytype == OBJARCINST)
		{
			if (drcb_debugflag)
				ttyputmsg(_("    Primitive arc %s:"), describearcinst(geom->entryaddr.ai));
			if (drcb_getarcinstshapes(state, intersect_list, geom->entryaddr.ai, itrans,
				lowx, highx, lowy, highy, hai) != 0) return(NONODEPROTO);
		}
	}

	/*
	 * !! Now go through the parent's intersect list 'parentintersectfacet' and select
	 * those shapes that intersect. Transform by itrans and push onto
	 * intersect_list. We allocate new copies -- if we used only
	 * pointers, then it would be faster but we'd have to keep refcnts
	 * to keep track of them when freeing.
	 */
	if (parentintersectfacet != NONODEPROTO)
	{
		searchkey = initsearch(lowx, highx, lowy, highy, parentintersectfacet);
		while((geom = nextobject(searchkey)) != NOGEOM)
		{
			REGISTER GEOM *g;
			SHAPE *oldshape, *newshape;

			/* we're only interested if the shape intersects the drc box */
			if (drcb_boxesintersect(geom->lowx, geom->highx, geom->lowy, geom->highy,
				lowx, highx, lowy, highy) == 0) continue;

			oldshape = (SHAPE *)geom->entryaddr.blind;
			g = allocgeom(dr_aid->cluster);
			if (g == NOGEOM) continue;
			*g = *geom;

			newshape = drcb_allocshape();
			if (newshape == 0)
			{
				ttyputerr(_("DRC error allocating memory for shape"));
				continue;
			}
			*newshape = *oldshape;
			g->entryaddr.blind = newshape;
			if (oldshape->hpp)
			{
				newshape->hpp = drcb_hashcopy(oldshape->hpp);
				if (newshape->hpp == 0) return(NONODEPROTO);
			}
			if (oldshape->poly != NOPOLYGON)
			{
				newshape->poly = drcb_allocpolygon(oldshape->poly->count);
				if (newshape->poly == NOPOLYGON) return(NONODEPROTO);
				polycopy(newshape->poly, oldshape->poly);
				xformpoly(newshape->poly, itrans);
			}
			xformbox(&(g->lowx), &(g->highx), &(g->lowy), &(g->highy), itrans);
			drcb_linkgeom(g, intersect_list);
			intersect_list->temp1++;
		}
	}
	drcb_hashdestroy(nhpp);
	/* !! How about the error list ? */

	return intersect_list;
}

/*
 * Descends through a complex node prototype and it's instances, finding
 * all elements intersecting the box described by lowx, highx, lowy,
 * highy and adding their polygons to plpp. itrans is the matrix
 * accumulated through the recursion down the tree that maps objects
 * back to the nodeinst that the search started from. mytrans is the
 * matrix accumulated at the same time describing the transformation
 * that maps objects back to the nodeproto being displayed.
 * Returns nonzero on error.
 */
static INTSML drcb_intersectsubtree(STATE *state, NODEPROTO *intersect_list, NODEPROTO *np,
	XARRAY itrans, XARRAY mytrans, INTBIG lowx, INTBIG highx, INTBIG lowy,
	INTBIG highy, HASHTABLE *hpp)
{
	REGISTER HASHTABLE *nhpp, *hai;
	REGISTER INTBIG searchkey;
	REGISTER GEOM *geom;
	XARRAY subtrans, temptrans1, temptrans2, temptrans3;
	INTBIG lx, hx, ly, hy;
	REGISTER NODEINST *ni;

	nhpp = drcb_hashcreate(HTABLE_PORT);
	if (nhpp == 0) return(1);
	hai = drcb_getarcnetworks(np, hpp, &state->netindex);
	if (hai == 0) return(1);

	searchkey = initsearch(lowx, highx, lowy, highy, np);
	while((geom = nextobject(searchkey)) != NOGEOM)
	{
		if (geom->entrytype == OBJNODEINST)
		{
			ni = geom->entryaddr.ni;

			/* Compute networks for the child */
			drcb_getnodenetworks(ni, hpp, hai, nhpp, &state->netindex);
			if (ni->proto->primindex == 0)
			{
				/*
				 * transform the intersecting box to the child
				 * nodeproto's coordinate system so we can recurse down
				 * the tree still intersecting with the same box.
				 */
				makerotI(ni, temptrans1);
				maketransI(ni, temptrans2);
				transmult(temptrans1, temptrans2, temptrans3);
				lx = lowx;
				hx = highx;
				ly = lowy;
				hy = highy;
				xformbox(&lx, &hx, &ly, &hy, temptrans3);

				/*
				 * Compute the inverse transform to apply to objects
				 * that intersect so they will get transformed back to
				 * the original nodeinst's coordinate space. Make a new
				 * itrans for ni in subtrans.
				 */
				maketrans(ni, temptrans1);
				makerot(ni, temptrans2);
				transmult(temptrans1, temptrans2, temptrans3);
				transmult(temptrans3, itrans, subtrans);

				/*
				 * mytrans transforms to the displayed prototype's
				 * coordinate system. Make a new mytrans for the ni in
				 * temptrans1 so we can pass it down as we recurse.
				 */
				transmult(temptrans3, mytrans, temptrans1);
				if (drcb_intersectsubtree(state, intersect_list, ni->proto, subtrans,
					temptrans1, lx, hx, ly, hy, nhpp) != 0) return(1);
			} else
			{
				if (drcb_getnodeinstshapes(state, intersect_list, ni, itrans,
					lowx, highx, lowy, highy, nhpp) != 0) return(1);
			}
			drcb_hashclear(nhpp);
		} else if (geom->entrytype == OBJARCINST)
		{
			if (drcb_getarcinstshapes(state, intersect_list, geom->entryaddr.ai, itrans,
				lowx, highx, lowy, highy, hai) != 0) return(1);
		}
	}
	drcb_hashdestroy(nhpp);
	drcb_hashdestroy(hai);
	return(0);
}

/*
 * Extracts the electrical shapes (presently only boxes) from ni,
 * transforms them by itrans, and inserts them into intersect_list.
 * Returns nonzero on error.
 */
static INTSML drcb_getnodeinstshapes(STATE *state, NODEPROTO *intersect_list, NODEINST *ni,
	XARRAY itrans, INTBIG lowx, INTBIG highx, INTBIG lowy, INTBIG highy, HASHTABLE *hpp)
{
	XARRAY rtrans, trans;
	INTBIG lx, hx, ly, hy, net;
	REGISTER INTBIG j, n;
	REGISTER LIBRARY *lib;
	REGISTER POLYLIST *polylist;
	REGISTER POLYGON *poly;
	REGISTER GEOM *geom;
	REGISTER TECHNOLOGY *tech;
	REGISTER SHAPE *shape;

	makerot(ni, rtrans);
	transmult(rtrans, itrans, trans);
	polylist = state->polylist;
	n = drcb_getnodeEpolys(ni, polylist);
	tech = ni->proto->tech;
	lib = ni->parent->cell->lib;
	for(j = 0; j < n; j++)
	{
		poly = polylist->polygons[j];
		if (poly->layer < 0) continue;
		if (maxdrcsurround(tech, lib, poly->layer) < 0) continue;

		/* transform the polygon for the current node */
		xformpoly(poly, rtrans);

		/* stop now if this box doesn't intersect the drc box */
		getbbox(poly, &lx, &hx, &ly, &hy);
		if (drcb_boxesintersect(lx, hx, ly, hy, lowx, highx, lowy, highy) == 0)
			continue;

		/* find the global electrical net for this layer */
		if (poly->portproto == NOPORTPROTO) net = NONET; else
			net = drcb_network(hpp, (char *)poly->portproto, 1);

		/*
		 * transform the box to the coordinates of the nodeinstance that
		 * initiated the search
		 */
		xformpoly(poly, itrans);

		/* save this in a shape structure */
		shape = drcb_allocshape();
		if (shape == 0) return(1);
		shape->layer = poly->layer;
		shape->entrytype = OBJNODEINST;
		shape->entryaddr.ni = ni;
		shape->net = net;
		transcpy(trans, shape->trans);

		/*
		 * we're wasting a fair bit of memory by copying an entire
		 * network table for a nodeinst for each shape in the intersect
		 * list.  Unfortunately, there's no simple way out of it; any
		 * more memory efficient scheme involves keeping reference
		 * counts for freeing, and sharing the tables as far as possible.
		 */
		shape->hpp = drcb_hashcopy(hpp);
		if (shape->hpp == 0) return(1);
		geom = allocgeom(dr_aid->cluster);
		if (geom == NOGEOM) return(1);
		geom->entrytype = 0;
		geom->entryaddr.blind = shape;
		if (isbox(poly, &lx, &hx, &ly, &hy) == 0)
		{
			getbbox(poly, &lx, &hx, &ly, &hy);
			shape->poly = drcb_allocpolygon(poly->count);
			if (shape->poly == NOPOLYGON) return(1);
			polycopy(shape->poly, poly);
		}
		geom->lowx = lx;
		geom->highx = hx;
		geom->lowy = ly;
		geom->highy = hy;
		drcb_linkgeom(geom, intersect_list);
		intersect_list->temp1++;
		geom = NOGEOM;
	}
	return(0);
}

/*
 * Extracts the electrical shapes (presently only boxes) from ai,
 * transforms them by trans, and inserts them into intersect_list.
 * Returns nonzero on error.
 */
static INTSML drcb_getarcinstshapes(STATE *state, NODEPROTO *intersect_list, ARCINST *ai,
	XARRAY trans, INTBIG lowx, INTBIG highx, INTBIG lowy, INTBIG highy, HASHTABLE *hai)
{
	INTBIG lx, hx, ly, hy;
	REGISTER INTBIG j, n;
	REGISTER POLYGON *poly;
	REGISTER POLYLIST *polylist;
	REGISTER GEOM *geom;
	REGISTER LIBRARY *lib;
	REGISTER TECHNOLOGY *tech;
	REGISTER SHAPE *shape;

	polylist = state->polylist;
	n = drcb_getarcpolys(ai, polylist);
	if (n < 0) return(0);
	tech = ai->proto->tech;
	lib = ai->parent->cell->lib;
	for(j = 0; j < n; j++)
	{
		poly = polylist->polygons[j];
		if (poly->layer < 0) continue;
		if (maxdrcsurround(tech, lib, poly->layer) < 0) continue;

		/* stop now if this box doesn't intersect the drc box */
		getbbox(poly, &lx, &hx, &ly, &hy);
		if (drcb_boxesintersect(lx, hx, ly, hy, lowx, highx, lowy, highy) == 0)
			continue;

		/*
		 * transform the box to the coordinates of the nodeinstance that
		 * initiated the search
		 */
		xformpoly(poly, trans);

		/* save this in a shape structure */
		shape = drcb_allocshape();
		if (shape == 0) return(1);
		shape->layer = poly->layer;
		shape->entrytype = OBJARCINST;
		shape->entryaddr.ai = ai;
		shape->net = drcb_network(hai, (char *)ai, 0);
		transcpy(trans, shape->trans);
		shape->hpp = NULL;
		geom = allocgeom(dr_aid->cluster);
		if (geom == NOGEOM) return(1);
		geom->entrytype = 0;
		geom->entryaddr.blind = shape;
		if (isbox(poly, &lx, &hx, &ly, &hy) == 0)
		{
			getbbox(poly, &lx, &hx, &ly, &hy);
			shape->poly = drcb_allocpolygon(poly->count);
			if (shape->poly == NOPOLYGON) return(1);
			polycopy(shape->poly, poly);
		}
		geom->lowx = lx;
		geom->highx = hx;
		geom->lowy = ly;
		geom->highy = hy;
		drcb_linkgeom(geom, intersect_list);
		intersect_list->temp1++;
		geom = NOGEOM;
	}
	return(0);
}

/*
 * routine to link geometry module "geom" into the R-tree.  The parent
 * nodeproto is in "parnt". (slightly modified from linkgeom -- this one
 * doesn't call boundobj to get the geom bounds -- it expects the ones in
 * geom to be valid when it is passed in.
 */
static void drcb_linkgeom(GEOM *geom, NODEPROTO *parnt)
{
	REGISTER RTNODE *rtn;
	REGISTER INTSML i, bestsubnode;
	REGISTER INTBIG bestexpand, area, newarea, expand, scaledown;
	INTBIG lxv, hxv, lyv, hyv;

	/* find the leaf that would expand least by adding this node */
	rtn = parnt->rtree;
	for(;;)
	{
		/* if R-tree node contains primitives, exit loop */
		if (rtn->flag != 0) break;

		/* compute scaling factor */
		scaledown = HUGEINT;
		for(i=0; i<rtn->total; i++)
		{
			db_rtnbbox(rtn, i, &lxv, &hxv, &lyv, &hyv);
			if (hxv-lxv < scaledown) scaledown = hxv - lxv;
			if (hyv-lyv < scaledown) scaledown = hyv - lyv;
		}
		if (scaledown <= 0) scaledown = 1;

		/* find sub-node that would expand the least */
		bestexpand = HUGEINT;
		for(i=0; i<rtn->total; i++)
		{
			/* get bounds and area of sub-node */
			db_rtnbbox(rtn, i, &lxv, &hxv, &lyv, &hyv);
			area = ((hxv - lxv) / scaledown) * ((hyv - lyv) / scaledown);

			/* get area of sub-node with new element */
			newarea = ((maxi(hxv, geom->highx) - mini(lxv, geom->lowx)) / scaledown) *
				((maxi(hyv, geom->highy) - mini(lyv, geom->lowy)) / scaledown);

			/* accumulate the least expansion */
			expand = newarea - area;
			if (expand > bestexpand) continue;
			bestexpand = expand;
			bestsubnode = i;
		}

		/* recurse down to sub-node that expanded least */
		rtn = (RTNODE *)rtn->pointers[bestsubnode];
	}

	/* add this geometry element to the correct leaf R-tree node */
	(void)db_addtortnode((UINTBIG)geom, rtn, parnt);
}

/************************ ACTUAL DESIGN RULE CHECKING ************************/

static INTBIG drcb_network(HASHTABLE *ht, char *cp, INTBIG type)
{
	REGISTER char *net;

	if (ht == 0) return(NONET);
	net = drcb_hashsearch(ht, cp);
	if (net == 0)
	{
		if (type != 0)
		{
			ttyputmsg(_("DRC error locating port %s in hashtable"),
				((PORTPROTO *)cp)->protoname);
		} else
		{
			ttyputmsg(_("DRC error locating arc %s in hashtable"),
				describearcinst((ARCINST *)cp));
		}
		return(NONET);
	}
	return((INTBIG)net);
}

static char *drcb_freehpp(char *key, char *datum, char *arg)
{
	drcb_hashdestroy((HASHTABLE *)datum);
	return(0);
}

/*
 * routine to make a design-rule check on polygon "poly" which is from object
 * "geom", on layer "layer", in technology "tech".
 * If "partial" is nonzero then only objects whose geometry modules are
 * greater than this one will be checked (to prevent any pair of objects
 * from being checked twice).  "state->nhpp" has the network tables
 * for geom if geom is a NODEINST, and can be NULL if geom is an ARCINST.
 * "state->hni" is a hash table containing the network tables for all
 * nodeinsts in facet, keyed by the nodeinst address.
 *
 * if "csap" is nonzero, this is a cross-hierarchical check, and the polygon
 * is actually from a pseudo-facet created to examine the vicinity of
 * an instance of "facet".  The routine examines everything in "facet" and compares
 * it to the polygon (which is in the coordinate space of the facet).
 *
 * Returns nonzero if an error was found.
 */
static INTSML drcb_badbox(CHECKSHAPEARG *csap, STATE *state, GEOM *geom,
	INTSML layer, TECHNOLOGY *tech, NODEPROTO *facet, POLYGON *poly, INTBIG net,
	INTSML partial)
{
	REGISTER GEOM *ngeom;
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *np;
	REGISTER ARCINST *ai;
	REGISTER INTBIG nnet, bound, search, dist;
	REGISTER INTBIG j, tot, touch;
	REGISTER INTSML ret;
	REGISTER POLYLIST *subpolylist;
	REGISTER POLYGON *npoly;
	XARRAY trans;
	REGISTER INTSML con, count;
	REGISTER HASHTABLE *pihpp;
	INTBIG lx, hx, ly, hy;

	/* see how far around the box it is necessary to search */
	bound = maxdrcsurround(tech, facet->cell->lib, layer);
	if (bound < 0) return(0);

	/* get bounds */
	getbbox(poly, &lx, &hx, &ly, &hy);

	/* search in the area surrounding the box */
	subpolylist = state->subpolylist;
	search = initsearch(lx-bound, hx+bound, ly-bound, hy+bound, facet);
	count = 0;
	for(;;)
	{
		if ((++count % 5) == 0)
		{
			if (stopping(STOPREASONDRC))
			{
				termsearch(search);
				return(0);
			}
		}
		if ((ngeom = nextobject(search)) == NOGEOM) break;
		if (partial != 0)
		{
			if (ngeom == geom) continue;
			if (ngeom < geom) continue;
		}

		/* see if the objects directly touch */
		touch = drcb_objtouch(ngeom, geom);

		if (ngeom->entrytype == OBJNODEINST)
		{
			ni = ngeom->entryaddr.ni;   np = ni->proto;
			if (drcb_debugflag)
			{
				if (csap != 0) ttyputmsg(_("      From facet %s, node %s"),
					describenodeproto(facet), describenodeinst(ni));
			}

			/* ignore nodes that are not primitive */
			if (np->primindex == 0) continue;

			/* don't check between technologies */
			if (np->tech != tech) continue;

			/* prepare to examine every layer in this nodeinst */
			makerot(ni, trans);

			if (state->hni == NULL) continue;
			pihpp = (HASHTABLE *)drcb_hashsearch(state->hni, (char *)ni);
			if (pihpp == NULL)
			{
				ttyputerr(_("DRC error 6 locating node %s in hash table"),
					describenodeinst(ni));
				continue;
			}

			/* get the shape of each nodeinst layer */
			tot = drcb_getnodeEpolys(ni, subpolylist);
			for(j=0; j<tot; j++)
			{
				npoly = subpolylist->polygons[j];
				if (npoly->layer < 0) continue;

				/* find the global electrical net for this layer */
				if (npoly->portproto == NOPORTPROTO) nnet = NONET; else
					nnet = drcb_network(pihpp, (char *)npoly->portproto, 1);

				/* see whether the two objects are electrically connected */
				if (nnet != NONET && nnet == net) con = 1; else con = 0;

				/* if they connect electrically and adjoin, don't check */
				if (con != 0 && touch != 0) continue;

				/* see how close they can get */
				dist = drcmindistance(tech, facet->cell->lib, layer, npoly->layer, con);
				if (dist < 0) continue;

				xformpoly(npoly, trans);
				if (drcb_debugflag)
				{
					if (csap != 0)
					{
						INTBIG ax, bx, ay, by;
						getbbox(npoly, &ax, &bx, &ay, &by);
						ttyputmsg(_("        Layer %s on net %d runs %s<=X<=%s"),
							layername(tech, npoly->layer), nnet, latoa(ax), latoa(bx));
					}
				}

				/* check the distance */
				ret = drcb_checkdist(csap, state, tech, layer, net, geom, poly, state->nhpp,
					npoly->layer, nnet, ngeom, npoly, pihpp, con, dist, facet);
				if (ret != 0) { termsearch(search);   return(1); }
			}
		} else
		{
			ai = ngeom->entryaddr.ai;

			/* don't check between technologies */
			if (ai->proto->tech != tech) continue;

			if (state->hai == NULL) continue;

			/* see whether the two objects are electrically connected */
			nnet = drcb_network(state->hai, (char *)ai, 0);
			if (net != NONET && nnet == net) con = 1; else con = 0;
			if (drcb_debugflag)
			{
				if (csap != 0) ttyputmsg(_("      From facet %s, arc %s, net %d"),
					describenodeproto(facet), describearcinst(ai), nnet);
			}

			/* if they connect electrically and adjoin, don't check */
			if (con != 0 && touch != 0) continue;

			/* get the shape of each arcinst layer */
			tot = drcb_getarcpolys(ai, subpolylist);
			for(j=0; j<tot; j++)
			{
				npoly = subpolylist->polygons[j];
				if (npoly->layer < 0) continue;

				/* see how close they can get */
				dist = drcmindistance(tech, facet->cell->lib, layer, npoly->layer, con);
				if (dist < 0) continue;

				/* check the distance */
				ret = drcb_checkdist(csap, state, tech, layer, net, geom, poly, state->nhpp,
					npoly->layer, nnet, ngeom, npoly, (HASHTABLE *)NULL, con, dist, facet);
				if (ret != 0) { termsearch(search);   return(1); }
			}
		}
	}
	return(0);
}

/*
 * Routine to compare:
 *    polygon "poly1" object "geom1" layer "layer1" network "net1" net table "hpp1"
 * with:
 *    polygon "poly2" object "geom2" layer "layer2" network "net2" net table "hpp2"
 * The polygons are both in technology "tech" and are connected if "con" is nonzero.
 * They cannot be less than "dist" apart.  "csap" is the hierarchical path information
 * and is zero if the two objects are at the same level of hierarchy.
 *
 * If "csap" is nonzero, this is a cross-hierarchical check.  Polygon 1 is from a
 * pseudo-facet created to examine the vicinity of an instance.  Polygon 2 is from
 * an object inside of the instance.  Checking is in the coordinate space of the instance.
 *
 * A positive return indicates that an error has been detected and reported.
 * The network tables are needed only if "geom1" or "geom2"
 * respectively are NODEINSTs.  (for cropping with shapes on the same
 * networks) If "geom1" or "geom2" is an ARCINST, we don't need the
 * corresponding network table, it can be NULL.
 */
static INTSML drcb_checkdist(CHECKSHAPEARG *csap, STATE *state, TECHNOLOGY *tech,
	INTSML layer1, INTBIG net1, GEOM *geom1, POLYGON *poly1, HASHTABLE *hpp1,
	INTSML layer2, INTBIG net2, GEOM *geom2, POLYGON *poly2, HASHTABLE *hpp2,
	INTSML con, INTBIG dist, NODEPROTO *facet)
{
	REGISTER INTSML isbox1, isbox2, withinfacet, maytouch;
	INTBIG lx1, hx1, ly1, hy1, lx2, hx2, ly2, hy2, xf1, yf1, xf2, yf2;
	char *msg;
	REGISTER POLYGON *origpoly1, *origpoly2;
	static POLYGON *poly1rebuild = NOPOLYGON, *poly2rebuild = NOPOLYGON;
	REGISTER INTBIG pd, fun, errtype, minwidth, lxb, hxb, lyb, hyb, actual;
	XARRAY trans1, trans2;

	/* make sure polygons exist */
	if (poly1rebuild == NOPOLYGON) poly1rebuild = allocstaticpolygon(4, dr_aid->cluster);
	if (poly2rebuild == NOPOLYGON) poly2rebuild = allocstaticpolygon(4, dr_aid->cluster);

	/* turn off flag that the nodeinst may be undersized */
	state->tinynodeinst = NONODEINST;

	origpoly1 = poly1;
	origpoly2 = poly2;
	isbox1 = isbox(poly1, &lx1, &hx1, &ly1, &hy1);
	if (isbox1 == 0) getbbox(poly1, &lx1, &hx1, &ly1, &hy1);
	isbox2 = isbox(poly2, &lx2, &hx2, &ly2, &hy2);
	if (isbox2 == 0) getbbox(poly2, &lx2, &hx2, &ly2, &hy2);

	/*
	 * special rule for allowing touching:
	 *   the layers are the same and either:
	 *     they connect and are *NOT* contact layers
	 *   or:
	 *     they don't connect and are implant layers (substrate/well)
	 */
	maytouch = 0;
	if (layer1 == layer2)
	{
		fun = layerfunction(tech, layer1) & LFTYPE;
		if (con != 0)
		{
			if (layeriscontact(fun) == 0) maytouch = 1;
		} else
		{
			if (fun == LFSUBSTRATE || fun == LFWELL || fun == LFIMPLANT) maytouch = 1;
		}
	}

	/* special code if both polygons are manhattan */
	if (isbox1 != 0 && isbox2 != 0)
	{
		if (maytouch != 0)
		{
			/* if they touch, it is acceptable */
			pd = maxi(maxi(lx2-hx1, lx1-hx2), maxi(ly2-hy1, ly1-hy2));
			if (pd <= 0)
			{
				minwidth = drcminwidth(tech, facet->cell->lib, layer1);
				lxb = maxi(lx1, lx2);
				hxb = mini(hx1, hx2);
				lyb = maxi(ly1, ly2);
				hyb = mini(hy1, hy2);
				actual = computedistance(lxb, lyb, hxb, hyb);
				if (actual == 0) return(0);
				if (actual >= minwidth) return(0);
				if (hxb-lxb > hyb-lyb)
				{
					/* horizontal abutment: check for minimum width */
					if (drcb_lookforpoints(geom1, geom2, layer1, facet, csap,
						lxb-1, lyb-1, lxb-1, hyb+1) != 0) return(0);
					if (drcb_lookforpoints(geom1, geom2, layer1, facet, csap,
						hxb+1, lyb-1, hxb+1, hyb+1) != 0) return(0);
					lyb -= minwidth/2;   hyb += minwidth/2;
				} else
				{
					/* vertical abutment: check for minimum width */
					if (drcb_lookforpoints(geom1, geom2, layer1, facet, csap,
						lxb-1, lyb-1, hxb+1, lyb-1) != 0) return(0);
					if (drcb_lookforpoints(geom1, geom2, layer1, facet, csap,
						lxb-1, hyb+1, hxb+1, hyb+1) != 0) return(0);
					lxb -= minwidth/2;   hxb += minwidth/2;
				}
				poly1rebuild->xv[0] = lxb;   poly1rebuild->yv[0] = lyb;
				poly1rebuild->xv[1] = hxb;   poly1rebuild->yv[1] = hyb;
				poly1rebuild->count = 2;
				poly1rebuild->style = FILLEDRECT;
				if (csap != 0)
					xformpoly(poly1rebuild, csap->vtrans);
				drcb_reporterror(MINWIDTHERROR, tech, 0, 0, minwidth, actual,
					poly1rebuild, geom1, layer1, NONET, NOPOLYGON, NOGEOM, 0, NONET);
				return(1);
			}
		}

		/* crop out parts of any arc that is covered by an adjoining node */
		if (geom1->entrytype == OBJARCINST)
		{
			if (drcb_croparcinst(state, geom1->entryaddr.ai, layer1, &lx1, &hx1, &ly1, &hy1) != 0)
				return(0);
		} else
		{
			if (drcb_cropnodeagainsarcs(state, geom1->entryaddr.ni, layer1,
				&lx1, &hx1, &ly1, &hy1) != 0) return(0);
		}
		if (geom2->entrytype == OBJARCINST)
		{
			if (drcb_croparcinst(state, geom2->entryaddr.ai, layer2, &lx2, &hx2, &ly2, &hy2) != 0)
				return(0);
		} else
		{
			if (drcb_cropnodeagainsarcs(state, geom2->entryaddr.ni, layer2,
				&lx2, &hx2, &ly2, &hy2) != 0) return(0);
		}

		/* crop out parts of a box covered by a similar layer on the other node */
		if (geom1->entrytype == OBJNODEINST)
		{
			if (csap == 0) makerot(geom1->entryaddr.ni, trans1); else
				transcpy(csap->dtrans, trans1);
			if (drcb_cropnodeinst(csap, state, geom1->entryaddr.ni, trans1, hpp1, net1, layer2, net2, geom2,
				&lx2, &hx2, &ly2, &hy2))
					return(0);
		}
		if (geom2->entrytype == OBJNODEINST)
		{
			makerot(geom2->entryaddr.ni, trans2);
			if (drcb_cropnodeinst(csap, state, geom2->entryaddr.ni, trans2, hpp2, net2, layer1, net1, geom1,
				&lx1, &hx1, &ly1, &hy1))
					return(0);
		}

		makerectpoly(lx1, hx1, ly1, hy1, poly1rebuild);
		poly1rebuild->style = FILLED;
		makerectpoly(lx2, hx2, ly2, hy2, poly2rebuild);
		poly2rebuild->style = FILLED;
		poly1 = poly1rebuild;
		poly2 = poly2rebuild;

		/* compute the distance */
		pd = maxi(maxi(lx2-hx1, lx1-hx2), maxi(ly2-hy1, ly1-hy2));
	} else
	{
		switch (poly1->style)
		{
			case FILLEDRECT:
			case CLOSEDRECT:
				maketruerect(poly1);
				break;
			case FILLED:
			case CLOSED:
			case CROSSED:
			case OPENED:
			case OPENEDT1:
			case OPENEDT2:
			case OPENEDT3:
			case VECTORS:
				break;
			default:
				return(0);
		}

		switch (poly2->style)
		{
			case FILLEDRECT:
			case CLOSEDRECT:
				maketruerect(poly2);
				break;
			case FILLED:
			case CLOSED:
			case CROSSED:
			case OPENED:
			case OPENEDT1:
			case OPENEDT2:
			case OPENEDT3:
			case VECTORS:
				break;
			default:
				return(0);
		}

		/* make sure polygons don't intersect */
		if (polyintersect(poly1, poly2) != 0) pd = 0; else
		{
			/* find distance between polygons */
			pd = polyseparation(poly1, poly2);
		}
	}

	/* see if the design rule is met */
	if (pd >= dist) return(0);
	errtype = SPACINGERROR;

	/* special cases if the layers are the same */
	if (layer1 == layer2)
	{
		/*
		 * special case: ignore errors between two active layers connected
		 * to either side of a field-effect transistor that is inside of
		 * the error area.
		 */
		if (drcb_activeontransistor(csap, geom1, layer1, net1, poly1,
			geom2, layer2, net2, poly2, state, tech) != 0) return(0);

		/* special case: check for "notch" */
		if (maytouch != 0)
		{
			/* if they touch, it is acceptable */
			if (pd <= 0) return(0);

			/* see if the notch is filled */
			if (drcb_findinterveningpoints(poly1, poly2, &xf1, &yf1, &xf2, &yf2) == 0) return(0);
			if (drcb_lookforpoints(geom1, geom2, layer1, facet, csap, xf1, yf1, xf2, yf2) != 0)
				return(0);
			errtype = NOTCHERROR;
		}
	}

	withinfacet = 1;
	if (csap != 0)
	{
		xformpoly(origpoly1, csap->vtrans);
		xformpoly(origpoly2, csap->vtrans);
		withinfacet = 0;
	}

	msg = 0;
	if (state->tinynodeinst != NONODEINST)
	{
		/* see if the node/arc that failed was involved in the error */
		if ((state->tinynodeinst->geom == geom1 || state->tinynodeinst->geom == geom2) &&
			(state->tinyobj == geom1 || state->tinyobj == geom2))
		{
			(void)initinfstr();
			(void)formatinfstr(_("%s is too small for the %s"),
				describenodeinst(state->tinynodeinst), geomname(state->tinyobj));
			(void)allocstring(&msg, returninfstr(), dr_aid->cluster);
		}
	}

	drcb_reporterror(errtype, tech, msg, withinfacet, dist, pd,
		origpoly1, geom1, layer1, net1,
		origpoly2, geom2, layer2, net2);
	return(1);
}

/*
 * Routine to explore the points (xf1,yf1) and (xf2,yf2) to see if there is
 * geometry on layer "layer" (in facet "facet").  Returns nonzero if there is.
 */
INTSML drcb_lookforpoints(GEOM *geom1, GEOM *geom2, INTBIG layer, NODEPROTO *facet, CHECKSHAPEARG *csap,
	INTBIG xf1, INTBIG yf1, INTBIG xf2, INTBIG yf2)
{
	INTBIG xf3, yf3, flx, fhx, fly, fhy, lx1, hx1, ly1, hy1;
	INTSML p1found, p2found, p3found;
	REGISTER GEOM *g;
	REGISTER INTBIG sea;
	REGISTER SHAPE *shape;

	xf3 = (xf1+xf2) / 2;
	yf3 = (yf1+yf2) / 2;

	/* compute bounds for searching inside facets */
	flx = mini(xf1, xf2);   fhx = maxi(xf1, xf2);
	fly = mini(yf1, yf2);   fhy = maxi(yf1, yf2);

	/* search the facet for geometry that fills the notch */
	p1found = p2found = p3found = 0;
	if (drcb_lookforlayer(facet, layer, el_matid, flx, fhx, fly, fhy,
		xf1, yf1, &p1found, xf2, yf2, &p2found, xf3, yf3, &p3found) != 0) return(1);

	/* in hierarchical situations, search the pseudo-facet for layers that cover the notch */
	if (csap != 0)
	{
		sea = initsearch(flx, fhx, fly, fhy, csap->intersectlist);
		if (sea < 0) return(0);
		for(;;)
		{
			g = nextobject(sea);
			if (g == NOGEOM) break;
			if (g == geom1 || g == geom2) continue;
			shape = (SHAPE *)g->entryaddr.blind;
			if (shape->layer != layer) continue;

			if (shape->poly != NOPOLYGON)
			{
				if (isinside(xf1, yf1, shape->poly) != 0) p1found = 1;
				if (isinside(xf2, yf2, shape->poly) != 0) p2found = 1;
				if (isinside(xf3, yf3, shape->poly) != 0) p3found = 1;
			} else
			{
				lx1 = g->lowx;   hx1 = g->highx;
				ly1 = g->lowy;   hy1 = g->highy;

				if (xf1 >= lx1 && xf1 <= hx1 && yf1 >= ly1 && yf1 <= hy1) p1found = 1;
				if (xf2 >= lx1 && xf2 <= hx1 && yf2 >= ly1 && yf2 <= hy1) p2found = 1;
				if (xf3 >= lx1 && xf3 <= hx1 && yf3 >= ly1 && yf3 <= hy1) p3found = 1;
			}
			if (p1found != 0 && p2found != 0 && p3found != 0)
			{
				termsearch(sea);
				return(1);
			}
		}
	}

	return(0);
}

/*
 * Routine to find two points between polygons "poly1" and "poly2" that can be used to test
 * for notches.  The points are returned in (xf1,yf1) and (xf2,yf2).  Returns zero if no
 * points exist in the gap between the polygons (becuase they don't have common facing edges).
 */
INTSML drcb_findinterveningpoints(POLYGON *poly1, POLYGON *poly2, INTBIG *xf1, INTBIG *yf1, INTBIG *xf2, INTBIG *yf2)
{
	REGISTER INTSML isbox1, isbox2;
	INTBIG lx1, hx1, ly1, hy1, lx2, hx2, ly2, hy2;
	REGISTER INTBIG xc, yc;

	if (isbox(poly1, &lx1, &hx1, &ly1, &hy1) != 0) isbox1 = 1; else isbox1 = 0;
	if (isbox(poly2, &lx2, &hx2, &ly2, &hy2) != 0) isbox2 = 1; else isbox2 = 0;
	if (isbox1 != 0 && isbox2 != 0)
	{
		/* handle vertical gap between polygons */
		if (lx1 > hx2 || lx2 > hx1)
		{
			/* see if the polygons are horizontally aligned */
			if (ly1 > hy2 || ly2 > hy1) return(0);
			if (lx1 > hx2) *xf1 = *xf2 = (lx1 + hx2) / 2; else
				*xf1 = *xf2 = (lx2 + hx1) / 2;
			*yf1 = maxi(ly1, ly2);
			*yf2 = mini(hy1, hy2);
		} else if (ly1 > hy2 || ly2 > hy1)
		{
			/* see if the polygons are horizontally aligned */
			if (lx1 > hx2 || lx2 > hx1) return(0);
			if (ly1 > hy2) *yf1 = *yf2 = (ly1 + hy2) / 2; else
				*yf1 = *yf2 = (ly2 + hy1) / 2;
			*xf1 = maxi(lx1, lx2);
			*xf2 = mini(hx1, hx2);
		}
		return(1);
	}

	/* handle nonmanhattan situation */
	/* find closest point on "poly2" to "poly1" */
	getcenter(poly1, xf2, yf2);
	closestpoint(poly2, xf2, yf2);

	/* find closest point on "poly1" to "poly2" */
	getcenter(poly2, xf1, yf1);
	closestpoint(poly1, xf1, yf1);

	/* get midpoint, use this for both points */
	xc = (*xf1 + *xf2) / 2;   yc = (*yf1 + *yf2) / 2;
	*xf1 = *xf2 = xc;
	*yf1 = *yf2 = yc;
	return(1);
}

/*
 * Routine to examine facet "facet" in the area (lx<=X<=hx, ly<=Y<=hy) for objects
 * on layer "layer".  Apply transformation "moretrans" to the objects.  If polygons are
 * found at (xf1,yf1) or (xf2,yf2) or (xf3,yf3) then sets "p1found/p2found/p3found" to 1.
 * If all locations are found, returns nonzero.
 */
INTSML drcb_lookforlayer(NODEPROTO *facet, INTBIG layer, XARRAY moretrans,
	INTBIG lx, INTBIG hx, INTBIG ly, INTBIG hy, INTBIG xf1, INTBIG yf1, INTSML *p1found,
	INTBIG xf2, INTBIG yf2, INTSML *p2found, INTBIG xf3, INTBIG yf3, INTSML *p3found)
{
	REGISTER INTBIG sea, i, tot;
	REGISTER GEOM *g;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	INTBIG newlx, newhx, newly, newhy, reasonable;
	XARRAY trans, rot, bound;
	static POLYGON *poly = NOPOLYGON;

	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, dr_aid->cluster);

	sea = initsearch(lx, hx, ly, hy, facet);
	for(;;)
	{
		g = nextobject(sea);
		if (g == NOGEOM) break;
		if (g->entrytype == OBJNODEINST)
		{
			ni = g->entryaddr.ni;
			if (ni->proto->primindex == 0)
			{
				/* compute bounding area inside of sub-facet */
				makerotI(ni, rot);
				maketransI(ni, trans);
				transmult(rot, trans, bound);
				newlx = lx;   newly = ly;   newhx = hx;   newhy = hy;
				xformbox(&newlx, &newhx, &newly, &newhy, bound);

				/* compute new matrix for sub-facet examination */
				maketrans(ni, trans);
				makerot(ni, rot);
				transmult(trans, rot, bound);
				transmult(bound, moretrans, trans);
				if (drcb_lookforlayer(ni->proto, layer, trans, newlx, newhx, newly, newhy,
					xf1, yf1, p1found, xf2, yf2, p2found, xf3, yf3, p3found) != 0)
						return(1);
				continue;
			}
			makerot(ni, rot);
			transmult(rot, moretrans, bound);
			tot = nodepolys(ni, &reasonable);
			if ((dr_options&DRCREASONABLE) != 0) tot = reasonable;
			for(i=0; i<tot; i++)
			{
				shapenodepoly(ni, i, poly);
				if (poly->layer != layer) continue;
				xformpoly(poly, bound);
				if (isinside(xf1, yf1, poly) != 0) *p1found = 1;
				if (isinside(xf2, yf2, poly) != 0) *p2found = 1;
				if (isinside(xf3, yf3, poly) != 0) *p3found = 1;
			}
		} else
		{
			ai = g->entryaddr.ai;
			tot = arcpolys(ai);
			for(i=0; i<tot; i++)
			{
				shapearcpoly(ai, i, poly);
				if (poly->layer != layer) continue;
				xformpoly(poly, moretrans);
				if (isinside(xf1, yf1, poly) != 0) *p1found = 1;
				if (isinside(xf2, yf2, poly) != 0) *p2found = 1;
				if (isinside(xf3, yf3, poly) != 0) *p3found = 1;
			}
		}
		if (*p1found != 0 && *p2found != 0 && *p3found != 0)
		{
			termsearch(sea);
			return(1);
		}
	}
	return(0);
}

/*
 * routine to see if the two boxes are active elements, connected to opposite
 * sides of a field-effect transistor that resides inside of the box area.
 * Returns nonzero if so.
 */
INTSML drcb_activeontransistor(CHECKSHAPEARG *csap, GEOM *geom1, INTSML layer1,
	INTBIG net1, POLYGON *poly1, GEOM *geom2, INTSML layer2, INTBIG net2,
	POLYGON *poly2, STATE *state, TECHNOLOGY *tech)
{
	REGISTER INTBIG fun, sea, net, xf3, yf3;
	INTBIG lx1, hx1, ly1, hy1, lx2, hx2, ly2, hy2, xf1, yf1, xf2, yf2;
	REGISTER INTSML on1, on2, p1found, p2found, p3found;
	REGISTER GEOM *g;
	REGISTER SHAPE *shape;
	REGISTER NODEINST *ni;
	REGISTER PORTARCINST *pi;
	REGISTER HASHTABLE *hpp;

	/* networks must be different */
	if (net1 == net2) return(0);

	/* layers must be active */
	fun = layerfunction(tech, layer1) & LFTYPE;
	if (fun != APDIFF && fun != APDIFFP && fun != APDIFFN && fun != APDIFFS && fun != APDIFFW)
		return(0);
	fun = layerfunction(tech, layer2) & LFTYPE;
	if (fun != APDIFF && fun != APDIFFP && fun != APDIFFN && fun != APDIFFS && fun != APDIFFW)
		return(0);

	/* search for intervening transistor in the facet */
	getbbox(poly1, &lx1, &hx1, &ly1, &hy1);
	getbbox(poly2, &lx2, &hx2, &ly2, &hy2);
	sea = initsearch(mini(lx1,lx2), maxi(hx1,hx2), mini(ly1,ly2), maxi(hy1,hy2), geomparent(geom2));
	if (sea < 0) return(0);
	for(;;)
	{
		g = nextobject(sea);
		if (g == NOGEOM) break;
		if (!isfet(g)) continue;

		/* got a transistor */
		ni = g->entryaddr.ni;

		if (state->hni == NULL) continue;
		hpp = (HASHTABLE *)drcb_hashsearch(state->hni, (char *)ni);
		if (hpp == NULL)
		{
			ttyputerr(_("DRC error 7 locating node %s in hash table (csap=%d)"),
				describenodeinst(ni), csap);
			continue;
		}

		on1 = on2 = 0;
		for(pi=ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		{
			net = drcb_network(hpp, (char *)pi->proto, 1);
			if (net == NONET) continue;
			if (net == net1) on1++;
			if (net == net2) on2++;
		}

		/* if either side is not connected, ignore this */
		if (on1 == 0 || on2 == 0) continue;

		/* transistor found that connects to both actives */
		termsearch(sea);
		return(1);
	}

	if (csap != 0)
	{
		/* working across hierarchical bounds: look for polysilicon layer in pseudo-facet */
		if (drcb_findinterveningpoints(poly1, poly2, &xf1, &yf1, &xf2, &yf2) == 0) return(0);
		xf3 = (xf1+xf2) / 2;
		yf3 = (yf1+yf2) / 2;
		p1found = p2found = p3found = 0;
		sea = initsearch(mini(xf1,xf2), maxi(xf1,xf2), mini(yf1,yf2), maxi(yf1,yf2), csap->intersectlist);
		if (sea < 0) return(0);
		for(;;)
		{
			g = nextobject(sea);
			if (g == NOGEOM) break;
			shape = (SHAPE *)g->entryaddr.blind;
			fun = layerfunction(tech, shape->layer) & LFTYPE;
			if (layerispoly(fun) == 0) continue;

			if (shape->poly != NOPOLYGON)
			{
				if (isinside(xf1, yf1, shape->poly) != 0) p1found = 1;
				if (isinside(xf2, yf2, shape->poly) != 0) p2found = 1;
				if (isinside(xf3, yf3, shape->poly) != 0) p3found = 1;
			} else
			{
				lx1 = g->lowx;   hx1 = g->highx;
				ly1 = g->lowy;   hy1 = g->highy;

				if (xf1 >= lx1 && xf1 <= hx1 && yf1 >= ly1 && yf1 <= hy1) p1found = 1;
				if (xf2 >= lx1 && xf2 <= hx1 && yf2 >= ly1 && yf2 <= hy1) p2found = 1;
				if (xf3 >= lx1 && xf3 <= hx1 && yf3 >= ly1 && yf3 <= hy1) p3found = 1;
			}
			if (p1found != 0 && p2found != 0 && p3found != 0)
			{
				termsearch(sea);
				return(1);
			}
		}
	}
	return(0);
}

/*
 * Routine to look at all arcs connected to node "ni", layer "nlayer" (from "*lx" <= X <' "*hx"
 * and "*ly" <= Y <' "*hy") and to remove overlap.
 */
static INTSML drcb_cropnodeagainsarcs(STATE *state, NODEINST *ni, INTSML nlayer,
	INTBIG *lx, INTBIG *hx, INTBIG *ly, INTBIG *hy)
{
	REGISTER PORTARCINST *pi;
	REGISTER ARCINST *ai;
	REGISTER POLYLIST *polylist;
	REGISTER POLYGON *poly;
	REGISTER INTBIG tot, j;
	INTBIG bx, by, ux, uy;
	REGISTER INTSML temp;

	polylist = state->croppolylist;
	for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
	{
		ai = pi->conarcinst;
		tot = drcb_getarcpolys(ai, polylist);
		for(j=0; j<tot; j++)
		{
			poly = polylist->polygons[j];
			if (poly->layer != nlayer) continue;

			/* warning: does not handle arbitrary polygons, only boxes */
			if (isbox(poly, &bx, &ux, &by, &uy) == 0) continue;
			temp = drcb_halfcropbox(lx, hx, ly, hy, bx, ux, by, uy);
			if (temp > 0) return(1);
		}
	}
	return(0);
}


/*
 * routine to crop the box on layer "nlayer", electrical index "nnet"
 * and bounds (lx-hx, ly-hy) against the nodeinst "ni".  Only those layers
 * in the nodeinst that are the same layer and the same electrical network
 * are checked.  The routine returns nonzero if the bounds are reduced
 * to nothing.
 */
static INTSML drcb_cropnodeinst(CHECKSHAPEARG *csap, STATE *state, NODEINST *ni, XARRAY trans, HASHTABLE *hpp,
	INTBIG ninet, INTSML nlayer, INTBIG nnet, GEOM *ngeom, INTBIG *lx, INTBIG *hx, INTBIG *ly, INTBIG *hy)
{
	INTBIG xl, xh, yl, yh, bx, ux, by, uy;
	REGISTER INTBIG tot, j, isconnected;
	REGISTER INTSML allgone;
	REGISTER INTBIG temp, net;
	REGISTER POLYLIST *polylist;
	REGISTER POLYGON *poly;

	polylist = state->croppolylist;
	tot = drcb_getnodeEpolys(ni, polylist);
	if (tot < 0) return(0);
	isconnected = 0;
	for(j=0; j<tot; j++)
	{
		poly = polylist->polygons[j];
		if (poly->layer != nlayer) continue;
		if (nnet != NONET)
		{
			if (poly->portproto == NOPORTPROTO) continue;
			net = drcb_network(hpp, (char *)poly->portproto, 1);
			if (net != NONET && net != nnet) continue;
		}
		isconnected++;
		break;
	}
	if (isconnected == 0) return(0);

	/* get the description of the nodeinst layers */
	allgone = 0;
	tot = drcb_getnodepolys(ni, polylist);
	if (tot < 0) return(0);
	for(j=0; j<tot; j++)
	{
		poly = polylist->polygons[j];
		if (poly->layer != nlayer) continue;

		/* warning: does not handle arbitrary polygons, only boxes */
		if (isbox(poly, &xl, &xh, &yl, &yh) == 0) continue;
		xform(xl, yl, &bx, &by, trans);
		xform(xh, yh, &ux, &uy, trans);
		if (bx > ux) { temp = bx; bx = ux; ux = temp; }
		if (by > uy) { temp = by; by = uy; uy = temp; }
		temp = cropbox(lx, hx, ly, hy, bx, ux, by, uy);
		if (temp > 0) { allgone = 1; break; }
		if (temp < 0)
		{
			state->tinynodeinst = ni;
			state->tinyobj = ngeom;
		}
	}
	return(allgone);
}

/*
 * routine to crop away any part of layer "lay" of arcinst "ai" that coincides
 * with a similar layer on a connecting nodeinst.  The bounds of the arcinst
 * are in the reference parameters (lx-hx, ly-hy).  The routine returns zero
 * normally, 1 if the arcinst is cropped into oblivion.
 */
static INTSML drcb_croparcinst(STATE *state, ARCINST *ai, INTSML lay,
	INTBIG *lx, INTBIG *hx, INTBIG *ly, INTBIG *hy)
{
	INTBIG bx, by, ux, uy, xl, xh, yl, yh;
	XARRAY trans;
	REGISTER INTBIG i, j, tot;
	REGISTER INTBIG temp;
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *np;
	REGISTER PORTPROTO *pp;
	REGISTER POLYLIST *polylist;
	REGISTER POLYGON *poly;

	polylist = state->croppolylist;
	for(i=0; i<2; i++)
	{
		/* find the primitive nodeinst at the true end of the portinst */
		ni = ai->end[i].nodeinst;   np = ni->proto;
		pp = ai->end[i].portarcinst->proto;
		while (np->primindex == 0)
		{
			ni = pp->subnodeinst;   np = ni->proto;
			pp = pp->subportproto;
		}

		makerot(ni, trans);
		tot = drcb_getnodepolys(ni, polylist);
		for(j=0; j<tot; j++)
		{
			poly = polylist->polygons[j];
			if (poly->layer != lay) continue;

			/* warning: does not handle arbitrary polygons, only boxes */
			if (isbox(poly, &xl, &xh, &yl, &yh) == 0) continue;
			xform(xl, yl, &bx, &by, trans);
			xform(xh, yh, &ux, &uy, trans);
			if (bx > ux) { temp = bx; bx = ux; ux = temp; }
			if (by > uy) { temp = by; by = uy; uy = temp; }

#if 0
			temp = cropbox(lx, hx, ly, hy, bx, ux, by, uy);
#else
			temp = drcb_halfcropbox(lx, hx, ly, hy, bx, ux, by, uy);
#endif
			if (temp > 0) return(1);
			if (temp < 0)
			{
				state->tinynodeinst = ni;
				state->tinyobj = ai->geom;
			}
		}
	}
	return(0);
}

/*
 * routine to crop the box in the reference parameters (lx-hx, ly-hy)
 * against the box in (bx-ux, by-uy).  If the box is cropped into oblivion,
 * the routine returns 1.  If the boxes overlap but cannot be cleanly cropped,
 * the routine returns -1.  Otherwise the box is cropped and zero is returned
 */
INTSML drcb_halfcropbox(INTBIG *lx, INTBIG *hx, INTBIG *ly, INTBIG *hy, INTBIG bx, INTBIG ux, INTBIG by,
	INTBIG uy)
{
	REGISTER INTSML crops;

	/* if the two boxes don't touch, just return */
	if (bx >= *hx || by >= *hy || ux <= *lx || uy <= *ly) return(0);

	/* if the box to be cropped is within the other, say so */
	if (bx <= *lx && ux >= *hx && by <= *ly && uy >= *hy) return(1);

	/* reduce (lx-hx,ly-hy) by (bx-ux,by-uy) */
	crops = 0;
	if (bx <= *lx && ux >= *hx)
	{
		/* it covers in X...crop in Y */
		if (uy >= *hy) *hy = (*hy + by) / 2;
		if (by <= *ly) *ly = (*ly + uy) / 2;
		crops++;
	}
	if (by <= *ly && uy >= *hy)
	{
		/* it covers in Y...crop in X */
		if (ux >= *hx) *hx = (*hx + bx) / 2;
		if (bx <= *lx) *lx = (*lx + ux) / 2;
		crops++;
	}
	if (crops == 0) return(-1);
	return(0);
}

/*
 * routine to tell whether the objects at geometry modules "geom1" and "geom2"
 * touch directly (that is, an arcinst connected to a nodeinst).  The routine
 * returns nonzero if they touch
 */
static INTSML drcb_objtouch(GEOM *geom1, GEOM *geom2)
{
	REGISTER GEOM *temp;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER INTSML i;

	if (geom1->entrytype == OBJNODEINST)
	{
		if (geom2->entrytype == OBJNODEINST) return(0);
		temp = geom1;   geom1 = geom2;   geom2 = temp;
	}
	if (geom2->entrytype == OBJARCINST)
		return(0);

	/* see if the arcinst at "geom1" touches the nodeinst at "geom2" */
	ni = geom2->entryaddr.ni;
	ai = geom1->entryaddr.ai;
	for(i=0; i<2; i++)
		if (ai->end[i].nodeinst == ni)
			return(1);
	return(0);
}

/***************** SUPPORT ******************/

/*
 * Initializes the port numbers for a toplevel nodeproto np, starting
 * the net numbers from *netindex, and storing all portprotos and their
 * nets in ht
 */
HASHTABLE *drcb_getinitnets(NODEPROTO *np, INTBIG *netindex)
{
	REGISTER PORTPROTO *pp, *spp;
	REGISTER char *cp;
	REGISTER HASHTABLE *ht;

	ht = drcb_hashcreate(HTABLE_PORT);
	if (ht == 0) return(0);

	/* re-assign net-list values, starting at each port of the top facet */
	for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		for(spp = np->firstportproto; spp != pp; spp = spp->nextportproto)
		{
			if (spp->network != pp->network) continue;

			/* this port is the same as another one, use same net */
			cp = drcb_hashsearch(ht, (char *)spp);
			if (cp == NULL)
			{
				ttyputerr(_("DRC error 8 locating port %s in hash table"), spp->protoname);
				return(0);
			}
			if (drcb_hashinsert(ht, (char *)pp, cp, 0) != 0)
			{
				ttyputerr(_("DRC error 9 inserting port %s into hash table"), pp->protoname);
				return(0);
			}
			break;
		}

		/* assign a new net number if the loop terminated normally */
		if (spp == pp)
		{
			cp = (char *)*netindex;
			if (drcb_hashinsert(ht, (char *)pp, cp, 0) != 0)
			{
				ttyputerr(_("DRC error 10 inserting port %s into hash table"), pp->protoname);
				return(0);
			}
			(*netindex)++;
		}
	}
	return(ht);
}

/*
 * generate a hash table of (arcinst, network numbers) pairs, keyed by
 * arcinst for a NODEPROTO from the hash table for portprotos (hpp) in
 * the parent NODEPROTO of the NODEINST.  The net numbers are propagated
 * to all arcs.  Returns the table in hai, a hash table passed in by
 * the parent.
 */
HASHTABLE *drcb_getarcnetworks(NODEPROTO *np, HASHTABLE *hpp, INTBIG *netindex)
{
	REGISTER char *cp;
	REGISTER ARCINST *ai;
	REGISTER PORTPROTO *pp;
	REGISTER PORTARCINST *pi;
	REGISTER HASHTABLE *hai;

	hai = drcb_hashcreate(HTABLE_NODEARC);
	if (hai == 0) return(0);

	/* propagate port numbers to arcs in the nodeproto */
	for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		/* find a connecting arc on the node that this port comes from */
		for(pi = pp->subnodeinst->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		{
			ai = pi->conarcinst;
			if (ai->network != pp->network) continue;
			if (drcb_hashsearch(hai, (char *)ai) != 0) continue;
			cp = drcb_hashsearch(hpp, (char *)pp);
			if (cp == NULL)
			{
				ttyputerr(_("DRC error 11 locating port %s in hash table"), pp->protoname);
				return(0);
			}
			drcb_flatprop2(ai, hai, cp);
			break;
		}
	}

	/* set node numbers on arcs that are local */
	for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		cp = drcb_hashsearch(hai, (char *)ai);
		if (cp == NULL)
		{
			drcb_flatprop2(ai, hai, (char *)*netindex);
			(*netindex)++;
		}
	}
	return(hai);
}

/*
 * routine to propagate the node number "cindex" to arcs
 * connected to arc "ai".
 */
void drcb_flatprop2(ARCINST *ai, HASHTABLE *hai, char *cindex)
{
	REGISTER ARCINST *oai;
	REGISTER NODEINST *ni;
	REGISTER PORTARCINST *pi;
	REGISTER INTSML i;

	/* set this arcinst to the current node number */
	if (drcb_hashinsert(hai, (char *)ai, cindex, 0) != 0)
	{
		ttyputerr(_("DRC error 12 inserting arc %s into hash table"), describearcinst(ai));
		return;
	}

	/* if signals do not flow through this arc, do not propagate */
	if (((ai->proto->userbits&AFUNCTION) >> AFUNCTIONSH) == APNONELEC) return;

	/* recursively set all arcs and nodes touching this */
	for(i=0; i<2; i++)
	{
		if ((ai->end[i].portarcinst->proto->userbits&PORTISOLATED) != 0) continue;
		ni = ai->end[i].nodeinst;
		for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		{
			/* select an arc that has not been examined */
			oai = pi->conarcinst;
			if (drcb_hashsearch(hai, (char *)oai) != 0) continue;

			/* see if the two ports connect electrically */
			if (ai->end[i].portarcinst->proto->network != pi->proto->network) continue;

			/* recurse on the nodes of this arc */
			drcb_flatprop2(oai, hai, cindex);
		}
	}
}

/*
 * generate a hash table of (portproto, network numbers) pairs, keyed by
 * portproto for a NODEINST from corresponding hash tables for
 * portprotos (hpp) and arcs (hai) in the NODEINST's parent proto.  The
 * net numbers are derived from the arcs and exports in the
 * NODEINST.  Returns the table in nhpp, a hash table passed in by the
 * parent.
 */
void drcb_getnodenetworks(NODEINST *ni, HASHTABLE *hpp, HASHTABLE *hai,
	HASHTABLE *nhpp, INTBIG *netindex)
{
	REGISTER char *cp;
	REGISTER PORTPROTO *pp, *spp;
	REGISTER PORTARCINST *pi;
	REGISTER PORTEXPINST *pe;

	for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
	{
		pp = pi->proto;
		if (drcb_hashsearch(nhpp, (char *)pp) != 0) continue;
		cp = drcb_hashsearch(hai, (char *)pi->conarcinst);
		if (cp == 0)
		{
			ttyputerr(_("DRC error 13 locating arc %s in hash table"),
				describearcinst(pi->conarcinst));
			return;
		}
		if (drcb_hashinsert(nhpp, (char *)pp, cp, 0) != 0)
		{
			ttyputerr(_("DRC error 14 inserting port %s into hash table"), pp->protoname);
			return;
		}
	}
	for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
	{
		pp = pe->proto;
		if (drcb_hashsearch(nhpp, (char *)pp) != 0) continue;
		cp = drcb_hashsearch(hpp, (char *)pe->exportproto);
		if (cp == 0)
		{
			ttyputerr(_("DRC error 15 locating port %s in hash table"),
				pe->exportproto->protoname);
			return;
		}
		if (drcb_hashinsert(nhpp, (char *)pp, cp, 0) != 0)
		{
			ttyputerr(_("DRC error 16 inserting port %s into hash table"), pp->protoname);
			return;
		}
	}

	/* look for unassigned ports and give new or copied network numbers */
	for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		if (drcb_hashsearch(nhpp, (char *)pp) != 0) continue;
		cp = 0;
		for(spp = ni->proto->firstportproto; spp != NOPORTPROTO;
			spp = spp->nextportproto)
		{
			if (spp->network != pp->network) continue;
			cp = drcb_hashsearch(nhpp, (char *)spp);
			if (cp == 0) continue;
			if (drcb_hashinsert(nhpp, (char *)pp, cp, 0) != 0)
			{
				ttyputerr(_("DRC error 17 inserting port %s into hash table"),
					pp->protoname);
				return;
			}
			break;
		}
		if (cp == 0)
		{
			cp = (char *)*netindex;
			if (drcb_hashinsert(nhpp, (char *)pp, cp, 0) != 0)
			{
				ttyputerr(_("DRC error 18 inserting port %s into hash table"),
					pp->protoname);
				return;
			}
			(*netindex)++;
		}
	}
}

/***************** MATHEMATICS ******************/

/*
 * Computes the intersection of the two boxes. Returns nonzero if they
 * intersect, zero if they do not intersect.  Intersection is strict:
 * if the boxes touch, the check returns nonzero.
 */
static INTSML drcb_boxesintersect(INTBIG lx1p, INTBIG hx1p, INTBIG ly1p, INTBIG hy1p,
	INTBIG lx2, INTBIG hx2, INTBIG ly2, INTBIG hy2)
{
	if (mini(hx1p, hx2) <= maxi(lx1p, lx2)) return(0);
	if (mini(hy1p, hy2) <= maxi(ly1p, ly2)) return(0);
	return(1);
}

/*
 * drcb_maxcheckable is as high as we'd ever want to check for
 * primes.  If you raise it, you have to generate more primes for
 * the small_primes table.  drcb_maxcheckable should not be a prime.
 */
static INTBIG drcb_maxcheckable = 20000000L;

/* This table contains all primes under sqrt(drcb_maxcheckable) */
static INTBIG small_primes[] = {
	2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
	61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127,
	131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191,
	193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257,
	263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331,
	337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401,
	409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467,
	479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563,
	569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631,
	641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709,
	719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797,
	809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877,
	881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967,
	971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033,
	1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097,
	1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181,
	1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249,
	1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307,
	1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423,
	1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481,
	1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549,
	1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609,
	1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693,
	1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759,
	1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861,
	1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931,
	1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003,
	2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083,
	2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143,
	2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243,
	2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311,
	2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383,
	2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459,
	2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551,
	2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657,
	2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707,
	2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777,
	2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851,
	2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939,
	2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023,
	3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119,
	3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209,
	3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301,
	3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361,
	3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461,
	3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533,
	3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607,
	3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677,
	3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767,
	3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851,
	3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923,
	3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013,
	4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093,
	4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177,
	4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259,
	4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349,
	4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 4447,
	4451, 4457, 4463,
};
static INTBIG n_small_primes = sizeof(small_primes) / sizeof(INTBIG);

/*
 * A fast and simple way to check if a number is prime - it
 * shouldn't be divisible by any prime less than it's square
 * root.
 */
INTSML drcb_isprime(INTBIG n)
{
	REGISTER INTBIG i, m;

	if ((n % 2) == 0) return(0);

	/* if we can't check it, assume it is false */
	if (n >= drcb_maxcheckable) return(0);

	for(i = 0; i < n_small_primes; i++)
	{
		m = small_primes[i];
		if (m * m >= n) break;
		if (n % m == 0) return(0);
	}
	return(1);
}

/*
 * returns the first prime over N, by checking all odd numbers
 * over N till it finds one
 */
INTBIG drcb_pickprime(INTBIG n)
{
	if (n >= drcb_maxcheckable) return(0);
	if (n % 2 == 0) n++;
	while(!drcb_isprime(n))
	{
		n += 2;
	}
	return n;
}

/***************** PRIMITIVE NODE/ARCINST TO SHAPES  ******************/

/*
 * These routines get all the polygons in a primitive instance, and
 * store them in the POLYLIST structure, returning a count of the number
 * of polygons.   They all return -1 if an allocation fails.
 */
static INTBIG drcb_getnodeEpolys(NODEINST *ni, POLYLIST *plist)
{
	REGISTER INTBIG tot, j;
	INTBIG reasonable;

	tot = nodeEpolys(ni, &reasonable);
	if ((dr_options&DRCREASONABLE) != 0) tot = reasonable;
	if (drcb_ensurepolylist(plist, tot) != 0) return(-1);
	for(j = 0; j < tot; j++)
		shapeEnodepoly(ni, j, plist->polygons[j]);
	return tot;
}

static INTBIG drcb_getnodepolys(NODEINST *ni, POLYLIST *plist)
{
	REGISTER INTBIG tot, j;
	INTBIG reasonable;

	tot = nodepolys(ni, &reasonable);
	if ((dr_options&DRCREASONABLE) != 0) tot = reasonable;
	if (drcb_ensurepolylist(plist, tot) != 0) return(-1);
	for(j = 0; j < tot; j++)
		shapenodepoly(ni, j, plist->polygons[j]);
	return tot;
}

static INTBIG drcb_getarcpolys(ARCINST *ai, POLYLIST *plist)
{
	REGISTER INTBIG tot, j;

	tot = arcpolys(ai);
	if (drcb_ensurepolylist(plist, tot) != 0) return(-1);
	for(j = 0; j < tot; j++)
		shapearcpoly(ai, j, plist->polygons[j]);
	return tot;
}

static void drcb_freepolylist(POLYLIST *list)
{
	REGISTER INTBIG i;

	for(i=0; i<list->polylisttotal; i++)
		drcb_freepolygon(list->polygons[i]);
	if (list->polylisttotal > 0)
		efree((char *)list->polygons);
	efree((char *)list);
}

/*
 * routine to accumulate a list of polygons at least "tot" long in the
 * polygon structure "list".  The routine returns nonzero if there is no
 * more memory.
 */
static INTSML drcb_ensurepolylist(POLYLIST *list, INTBIG tot)
{
	REGISTER POLYGON **newpolylist;
	REGISTER INTBIG j;

	/* make sure the list is the right size */
	if (list->polylisttotal < tot)
	{
		newpolylist = (POLYGON **)emalloc(tot * (sizeof (POLYGON *)),
			dr_aid->cluster);
		if (newpolylist == 0)
		{
			ttyputerr(_("DRC error allocating memory for polygons"));
			return(1);
		}
		for(j=0; j<tot; j++) newpolylist[j] = NOPOLYGON;
		if (list->polylisttotal != 0)
		{
			for(j=0; j<list->polylisttotal; j++)
				newpolylist[j] = list->polygons[j];
			efree((char *)list->polygons);
		}
		list->polygons = newpolylist;
		list->polylisttotal = tot;
	}

	/* make sure there is a polygon for each entry in the list */
	for(j=0; j<tot; j++) if (list->polygons[j] == NOPOLYGON)
		list->polygons[j] = drcb_allocpolygon(4);
	return(0);
}

/*********************************** HASH TABLE ***********************************/

/*
 * Creates a hashtable 'size' elements long. size should be a prime
 * number. The user should not poke around inside the hash table
 */
HASHTABLE *drcb_hashcreate(INTSML size)
{
	REGISTER HASHTABLE *newh;
	REGISTER INTBIG i;

	/* Minimum size for this to work */
	if (size < 5) return(0);

	/* allocate the table */
	newh = drcb_allochashtable();
	if (newh == NULL) return(0);
	newh->table = drcb_allochashrec(size);
	if (newh->table == NULL)
	{
		drcb_freehashtable(newh);
		return(0);
	}

	/* initialize the table */
	for(i=0; i<size; i++) newh->table[i].key = newh->table[i].datum = 0;
	newh->size = size;
	newh->probe = size / 3;
	if (newh->probe * 3 == size) newh->probe--;
	if (newh->probe % 2 == 0) newh->probe--;
	return(newh);
}

/*
 * Frees a created hash table. It first walks the hash table, calling
 * free_proc for every element in the table. free_proc should return
 * NULL on each element or the free walk will terminate. The table will
 * be freed anyway.
 */
void drcb_hashdestroy(HASHTABLE *hash)
{
	drcb_freehashrec(hash->table, hash->size);
	drcb_freehashtable(hash);
}

/*
 * Even though this function can be accomplished with drcb_hashwalk, it's
 * useful and small enough to deserve special-casing.
 */
void drcb_hashclear(HASHTABLE *hash)
{
	REGISTER INTBIG i;

	for(i = hash->size - 1; i >= 0; i--)
	{
		hash->table[i].key = NULL;
		hash->table[i].datum = NULL;
	}
}

/*
 * copies a table to a new table.  returns 0 if it failed, or the new
 * table if it succeeded.
 */
HASHTABLE *drcb_hashcopy(HASHTABLE *hash)
{
	REGISTER HASHTABLE *newh;
	REGISTER INTBIG n;
	REGISTER HASHREC *dhrp, *shrp;

	newh = drcb_hashcreate((INTSML)hash->size);
	if (newh == NULL) return(0);
	for(dhrp = newh->table, shrp = hash->table, n = hash->size; n > 0; n--)
	{
		dhrp->key = shrp->key;
		dhrp->datum = shrp->datum;
		dhrp++;
		shrp++;
	}
	return(newh);
}

static INTBIG drcb_hashindex(char *s)
{
	REGISTER INTBIG shift, hindex;

	hindex = 0;
	shift = 0;
	while (*s != 0)
	{
		hindex ^= *(s++) << shift;
		shift = 8 - shift;
	}
	return(hindex);
}

/*
 * calls proc(key, datum, arg) for each item in the hash table. proc may
 * return a char *, in which case the walk terminates and the value is
 * returned to the caller
 */
char *drcb_hashwalk(HASHTABLE *hash, char *(*proc)(char*, char*, char*), char *arg)
{
	REGISTER INTBIG i;
	REGISTER char *ret;

	for(i = 0; i < hash->size; i++)
	{
		if (hash->table[i].key != NULL)
		{
			ret = (*proc)(hash->table[i].key, hash->table[i].datum, arg);
			if (ret != NULL) return ret;
		}
	}
	return(0);
}

static char *drcb_growhook(char *key, char *datum, char *arg)
{
	(void)drcb_hashinsert((HASHTABLE *)arg, key, datum, 0);
	return(0);
}

/*
 * grows a table to a new size.  Returns 0 if it failed, in which case
 * the old table is still intact, 1 if it succeeds, in which case hash
 * will contain the new table
 */
INTSML drcb_hashgrow(HASHTABLE *hash, INTSML newsize)
{
	REGISTER HASHTABLE *newh;

	newh = drcb_hashcreate(newsize);
	if (newh == NULL) return(0);
	(void)drcb_hashwalk(hash, drcb_growhook, (char *)newh);

	drcb_freehashrec(hash->table, hash->size);
	*hash = *newh;
	drcb_freehashtable(newh);
	return(1);
}

/*
 * inserts a pointer item in the hash table. returns 0 if it succeeds, If
 * replace is true, and the key already exists in the table, then it
 * replaces the old datum with the new one. If replace is false, the it
 * only inserts the datum if the key is not found in the table and
 * there's enough space. if replace is false, it returns HASH_EXISTS if
 * it finds the item in the table already, HASH_FULL if the table has no
 * room in it and it was unable to grow it to a new size.
 */
INTBIG drcb_hashinsert(HASHTABLE *hash, char *key, char *datum, INTBIG replace)
{
	REGISTER INTBIG hindex, countdown;
	REGISTER HASHREC *hashptr;

	hashptr = hash->table;
	hindex = ((INTBIG)key) % hash->size;
	hashptr += hindex;

	/*
	 * we should probably make countdown smaller so we don't walk
	 * through the entire table when it gets too full
	 */
	countdown = hash->size;
	while (hashptr->key && --countdown)
	{
		if (hashptr->key == key)
		{
			if (replace)
			{
				hashptr->datum = datum;
				return(0);
			} else
			{
				return HASH_EXISTS;
			}
		}
		hindex = (hindex + hash->probe) % hash->size;
		hashptr = hash->table + hindex;
	}

	if (countdown)
	{
		hashptr->key = key;
		hashptr->datum = datum;
	} else
	{
		INTBIG newsize = drcb_pickprime(hash->size * 3);
		if (newsize == 0) return HASH_FULL;
		if (drcb_hashgrow(hash, (INTSML)newsize) == 0) return HASH_FULL;
		return drcb_hashinsert(hash, key, datum, replace);
	}
	return(0);
}

/*
 * looks for key in the hash table.  Key is treated as an opaque pointer
 * value
 */
static HASHREC *drcb_findhashrec(HASHTABLE *hash, char *key)
{
	REGISTER INTBIG hindex, countdown;
	REGISTER HASHREC *hashptr;

	hashptr = hash->table;
	hindex = ((INTBIG)key) % hash->size;
	hashptr += hindex;

	countdown = hash->size;
	while (hashptr->key && hashptr->key != key && --countdown)
	{
		hindex = (hindex + hash->probe) % hash->size;
		hashptr = hash->table + hindex;
	}

	if (hashptr->key && countdown) return hashptr;
	return(0);
}

char *drcb_hashsearch(HASHTABLE *hash, char *key)
{
	REGISTER HASHREC *hashptr;

	hashptr = drcb_findhashrec(hash, key);
	if (hashptr) return hashptr->datum;
	return(0);
}

/*
 * This will find key in hash, return the datum, return the key in
 * keyret, and delete the entry from the hash table.
 */
char *drcb_hashdelete(HASHTABLE *hash, char *key, char **keyret)
{
	REGISTER INTBIG hindex, hval, countdown;
	REGISTER char *ret;
	REGISTER HASHREC *hashptr;

	hashptr = drcb_findhashrec(hash, key);
	if (hashptr == 0) return(0);

	ret = hashptr->datum;
	*keyret = hashptr->key;
	hval = hindex = ((INTBIG)key) % hash->size;

	countdown = hash->size;
	while (--countdown)
	{
		HASHREC *nhp;

		hindex = (hindex + hash->probe) % hash->size;
		nhp = hash->table + hindex;
		if (nhp && (((INTBIG)nhp->key) % hash->size) == hval)
		{
			*hashptr = *nhp;
		} else
		{
			hashptr->key = hashptr->datum = NULL;
			break;
		}
	}
	return ret;
}

/************************ ALLOCATION ************************/

/* Frees the list of intersecting elements in a node instance */
static void drcb_freeintersectingelements(NODEPROTO *np)
{
	if (np == NONODEPROTO) return;
	drcb_freertree(np->rtree);
	np->rtree = NORTNODE;
	freenodeproto(np);
}

/*
 * Frees an R Tree hierarchy - traversing the tree in a depth first
 * search free-ing all nodes and their contents. This should probably be
 * an Electric database routine, but right now, it has knowledge of the
 * shape wired in here.  Should be abstracted out into a hook routine!!
 */
static void drcb_freertree(RTNODE *rtn)
{
	REGISTER INTSML j;
	REGISTER SHAPE *shape;

	if (rtn->flag != 0)
	{
		for(j = 0; j < rtn->total; j++)
		{
			/* free shape */
			shape = (SHAPE *)(((GEOM *)(rtn->pointers[j]))->entryaddr.blind);
			if (shape->hpp)
				drcb_hashdestroy(shape->hpp);
			if (shape->poly != NOPOLYGON)
				drcb_freepolygon(shape->poly);
			drcb_freeshape(shape);
			freegeom((GEOM *)(rtn->pointers[j]));
			rtn->pointers[j] = 0;
		}
		freertnode(rtn);
		return;
	}

	for(j = 0; j < rtn->total; j++)
	{
		drcb_freertree((RTNODE *)rtn->pointers[j]);
		rtn->pointers[j] = 0;
	}
	freertnode(rtn);
}

SHAPE *drcb_allocshape(void)
{
	REGISTER SHAPE *shape;

	if (drcb_freeshapes != NOSHAPE)
	{
		shape = drcb_freeshapes;
		drcb_freeshapes = (SHAPE *)shape->hpp;
	} else
	{
		shape = (SHAPE *)emalloc(sizeof(SHAPE), dr_aid->cluster);
	}
	shape->poly = NOPOLYGON;
	return(shape);
}

void drcb_freeshape(SHAPE *shape)
{
	shape->hpp = (HASHTABLE *)drcb_freeshapes;
	drcb_freeshapes = shape;
}

POLYGON *drcb_allocpolygon(INTSML count)
{
	REGISTER POLYGON *poly;

	if (drcb_freepolygons != NOPOLYGON)
	{
		poly = drcb_freepolygons;
		drcb_freepolygons = poly->nextpolygon;
		if (poly->count < count) extendpolygon(poly, count);
	} else
	{
		poly = allocpolygon(count, dr_aid->cluster);
	}
	return(poly);
}

void drcb_freepolygon(POLYGON *poly)
{
	poly->nextpolygon = drcb_freepolygons;
	drcb_freepolygons = poly;
}

HASHTABLE *drcb_allochashtable(void)
{
	REGISTER HASHTABLE *ht;

	if (drcb_freehashtables != NOHASHTABLE)
	{
		ht = drcb_freehashtables;
		drcb_freehashtables = (HASHTABLE *)ht->table;
	} else
	{
		ht = (HASHTABLE *)emalloc(sizeof(HASHTABLE), dr_aid->cluster);
	}
	return(ht);
}

void drcb_freehashtable(HASHTABLE *ht)
{
	ht->table = (HASHREC *)drcb_freehashtables;
	drcb_freehashtables = ht;
}

HASHREC *drcb_allochashrec(INTBIG size)
{
	REGISTER HASHREC *hr;

	if (size == HTABLE_NODEARC && drcb_freehashrecni != NOHASHREC)
	{
		hr = drcb_freehashrecni;
		drcb_freehashrecni = (HASHREC *)hr[0].key;
		return(hr);
	}
	if (size == HTABLE_PORT && drcb_freehashrecpp != NOHASHREC)
	{
		hr = drcb_freehashrecpp;
		drcb_freehashrecpp = (HASHREC *)hr[0].key;
		return(hr);
	}

	hr = (HASHREC *)emalloc(size * (sizeof (HASHREC)), dr_aid->cluster);
	return(hr);
}

void drcb_freehashrec(HASHREC *hr, INTBIG size)
{
	if (size == HTABLE_NODEARC)
	{
		hr[0].key = (char *)drcb_freehashrecni;
		drcb_freehashrecni = hr;
	} else if (size == HTABLE_PORT)
	{
		hr[0].key = (char *)drcb_freehashrecpp;
		drcb_freehashrecpp = hr;
	} else
	{
		efree((char *)hr);
	}
}

/************************ ERROR REPORTING ************************/

/* Adds details about an error to the error list */
static void drcb_reporterror(INTBIG errtype, TECHNOLOGY *tech, char *msg,
	INTSML withinfacet, INTBIG limit, INTBIG actual,
	POLYGON *poly1, GEOM *geom1, INTSML layer1, INTBIG net1,
	POLYGON *poly2, GEOM *geom2, INTSML layer2, INTBIG net2)
{
	REGISTER NODEPROTO *np, *topfacet, *np1, *np2;
	REGISTER VARIABLE *var;
	REGISTER INTBIG i, len;
	REGISTER GEOM *p1, *p2;
	REGISTER void *err;

	if (drcb_debugflag)
	{
		switch (errtype)
		{
			case SPACINGERROR:  ttyputmsg(_("****SPACING ERROR****"));  break;
			case NOTCHERROR:    ttyputmsg(_("****NOTCH ERROR****"));    break;
			case MINWIDTHERROR: ttyputmsg(_("****WIDTH ERROR****"));    break;
		}
	}

	/* if this error is being ignored, don't record it */
	if ((errtype == SPACINGERROR || errtype == NOTCHERROR) && withinfacet != 0)
	{
		np = geomparent(geom1);
		var = getvalkey((INTBIG)np, VNODEPROTO, VGEOM|VISARRAY, dr_ignore_listkey);
		if (var != NOVARIABLE)
		{
			len = getlength(var);
			for(i=0; i<len; i += 2)
			{
				p1 = ((GEOM **)var->addr)[i];
				p2 = ((GEOM **)var->addr)[i+1];
				if (p1 == geom1 && p2 == geom2) return;
				if (p1 == geom2 && p2 == geom1) return;
			}
		}
	}
	if (withinfacet != 0) topfacet = geomparent(geom1); else
		topfacet = drcb_topfacet;

	/* highlight the error */
	if (drcb_hierarchicalcheck != 0 && withinfacet == 0 && drcb_topfacetalways == topfacet)
	{
		if (numerrors() == 0)
			(void)askaid(us_aid, "clear");
		drcb_highlighterror(poly1, poly2, topfacet);
	}

	(void)initinfstr();
	np1 = geomparent(geom1);
	if (errtype == SPACINGERROR || errtype == NOTCHERROR)
	{
		/* describe spacing width error */
		if (errtype == SPACINGERROR) (void)addstringtoinfstr(_("Spacing")); else
			(void)addstringtoinfstr(_("Notch"));
		if (layer1 == layer2)
			(void)formatinfstr(_(" (layer %s)"), layername(tech, layer1));
		(void)addstringtoinfstr(": ");
		np2 = geomparent(geom2);
		if (np1 != np2)
		{
			(void)formatinfstr(_("facet %s, "), describenodeproto(np1));
		} else if (np1 != topfacet)
		{
			(void)formatinfstr(_("[in facet %s]"), describenodeproto(np1));
		}
		if (geom1->entrytype == OBJNODEINST)
			(void)formatinfstr(_("node %s"), describenodeinst(geom1->entryaddr.ni)); else
				(void)formatinfstr(_("arc %s"), describearcinst(geom1->entryaddr.ai));
		if (layer1 != layer2)
			(void)formatinfstr(_(", layer %s"), layername(tech, layer1));

		(void)formatinfstr(_(" LESS (BY %s) THAN %s TO "), latoa(limit-actual), latoa(limit));

		if (np1 != np2)
			(void)formatinfstr(_("facet %s, "), describenodeproto(np2));
		if (geom2->entrytype == OBJNODEINST)
			(void)formatinfstr(_("node %s"), describenodeinst(geom2->entryaddr.ni)); else
				(void)formatinfstr(_("arc %s"), describearcinst(geom2->entryaddr.ai));
		if (layer1 != layer2)
			(void)formatinfstr(_(", layer %s"), layername(tech, layer2));
		if (msg != NULL)
		{
			(void)addstringtoinfstr("; ");
			(void)addstringtoinfstr(msg);
		}
	} else
	{
		/* describe minimum width error */
		(void)formatinfstr(_("Minimum width error: facet %s"), describenodeproto(np1));
		if (geom1->entrytype == OBJNODEINST)
		{
			(void)formatinfstr(_(", node %s"), describenodeinst(geom1->entryaddr.ni));
		} else
		{
			(void)formatinfstr(_(", arc %s"), describearcinst(geom1->entryaddr.ai));
		}
		(void)formatinfstr(_(", layer %s"), layername(tech, layer1));

		(void)formatinfstr(_(" LESS THAN %s WIDE (IS %s)"), latoa(limit), latoa(actual));
	}
	err = logerror(returninfstr(), topfacet, mini(layer1, layer2));
	if (err == 0)
	{
		ttyputerr(_("DRC error allocating memory for error"));
		return;
	}
	(void)addpolytoerror(err, poly1);
	if (poly2 != NOPOLYGON) (void)addpolytoerror(err, poly2);
	(void)addgeomtoerror(err, geom1, 0);
	if (geom2 != NOGEOM) (void)addgeomtoerror(err, geom2, 0);
}

void drcb_highlighterror(POLYGON *poly1, POLYGON *poly2, NODEPROTO *facet)
{
	INTBIG lx, hx, ly, hy;
	REGISTER INTSML i, prev;

	if (isbox(poly1, &lx, &hx, &ly, &hy) != 0)
	{
		(void)askaid(us_aid, "show-area", lx, hx, ly, hy, facet);
	} else
	{
		for(i=0; i<poly1->count; i++)
		{
			if (i == 0) prev = poly1->count-1; else prev = i-1;
			(void)askaid(us_aid, "show-line", poly1->xv[prev], poly1->yv[prev],
				poly1->xv[i], poly1->yv[i], facet);
		}
	}
	if (poly2 != NOPOLYGON)
	{
		if (isbox(poly2, &lx, &hx, &ly, &hy) != 0)
		{
			(void)askaid(us_aid, "show-area", lx, hx, ly, hy, facet);
		} else
		{
			for(i=0; i<poly2->count; i++)
			{
				if (i == 0) prev = poly2->count-1; else prev = i-1;
				(void)askaid(us_aid, "show-line", poly2->xv[prev], poly2->yv[prev],
					poly2->xv[i], poly2->yv[i], facet);
			}
		}
	}
	flushscreen();
}

#endif  /* DRCAID - at top */
