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

#include "global.h"
#include "database.h"
#include "egraphics.h"
#include "tech.h"
#include "tecgen.h"
#include "tecart.h"
#include "tecschem.h"
#include "efunction.h"
#include <math.h>

       INTSML      tech_realpolys;					/* polygon count without displayable variables */

/* cached variables */
static INTBIG     *tech_node_widoff = 0;			/* cache for "nodesizeoffset" */
static INTBIG     *tech_layer_function = 0;			/* cache for "layerfunction" */
static INTBIG     *tech_layer_names = 0;			/* cache for "layername" */
static INTBIG     *tech_arc_widoff = 0;				/* cache for "arcwidthoffset" */
static INTBIG     *tech_drcmaxdistances = 0;		/* cache for "maxdrcsurround" */
static INTBIG     *tech_drcconndistance = 0;		/* cache for "drcmindistance" */
static INTBIG     *tech_drcuncondistance = 0;		/* cache for "drcmindistance" */
static INTBIG     *tech_drcminwidth = 0;			/* cache for "drcminwidth" */
static INTBIG      tech_techlayer3dheightkey;		/* cache for "TECH_layer_3dheight" */
static INTBIG      tech_techlayer3dthicknesskey;	/* cache for "TECH_layer_3dthickness" */
static TECHNOLOGY *tech_3dcurtech = NOTECHNOLOGY;
static VARIABLE   *tech_3dcurthicknessvar;
static VARIABLE   *tech_3dcurheightvar;


#define NOROUTINELIST ((ROUTINELIST *)-1)

typedef struct Iroutinelist
{
	void (*routine)(void);
	struct Iroutinelist *nextroutinelist;
} ROUTINELIST;
static ROUTINELIST *tech_firstcache = NOROUTINELIST;

/* prototypes for local routines */
void tech_initmaxdrcsurround(void);
void tech_initnodesizeoffset(void);
void tech_initlayerfunction(void);
void tech_initlayername(void);
void tech_initarcwidthoffset(void);
INTBIG tech_getdrcmindistance(TECHNOLOGY*, INTSML, INTSML, INTSML);
void tech_3ddefaultlayerheight(TECHNOLOGY *tech);

/*
 * Routine to free all memory associated with this module.
 */
void db_freetechnologymemory(void)
{
	REGISTER ROUTINELIST *rl;

	while (tech_firstcache != NOROUTINELIST)
	{
		rl = tech_firstcache;
		tech_firstcache = tech_firstcache->nextroutinelist;
		efree((char *)rl);
	}

	if (tech_node_widoff != 0) efree((char *)tech_node_widoff);
	if (tech_layer_function != 0) efree((char *)tech_layer_function);
	if (tech_layer_names != 0) efree((char *)tech_layer_names);
	if (tech_arc_widoff != 0) efree((char *)tech_arc_widoff);
	if (tech_drcmaxdistances != 0) efree((char *)tech_drcmaxdistances);
	if (tech_drcconndistance != 0) efree((char *)tech_drcconndistance);
	if (tech_drcuncondistance != 0) efree((char *)tech_drcuncondistance);
	if (tech_drcminwidth != 0) efree((char *)tech_drcminwidth);
}

/******************** TECHNOLOGY ALLOCATION ********************/

/*
 * routine to allocate a technology from memory cluster "cluster" and return its
 * address.  The routine returns NOTECHNOLOGY if allocation fails.
 */
TECHNOLOGY *alloctechnology(CLUSTER *cluster)
{
	REGISTER TECHNOLOGY *tech;

	tech = (TECHNOLOGY *)emalloc((sizeof (TECHNOLOGY)), cluster);
	if (tech == 0) return((TECHNOLOGY *)db_error(DBNOMEM|DBALLOCTECHNOLOGY));
	tech->techname = NOSTRING;
	tech->techindex = 0;
	tech->deflambda = 2000;
	tech->firstnodeproto = NONODEPROTO;
	tech->firstarcproto = NOARCPROTO;
	tech->firstvar = NOVARIABLE;
	tech->numvar = 0;
	tech->parse = NOCOMCOMP;
	tech->cluster = cluster;
	tech->techdescript = NOSTRING;
	tech->init = 0;
	tech->term = 0;
	tech->setmode = 0;
	tech->request = 0;
	tech->paramnode = 0;
	tech->nodepolys = 0;
	tech->nodeEpolys = 0;
	tech->shapenodepoly = 0;
	tech->shapeEnodepoly = 0;
	tech->nodesizeoffset = 0;
	tech->shapeportpoly = 0;
	tech->arcpolys = 0;
	tech->shapearcpoly = 0;
	tech->arcwidthoffset = 0;
	tech->nexttechnology = NOTECHNOLOGY;
	tech->userbits = tech->temp1 = tech->temp2 = 0;
	tech->variables = (TECH_VARIABLES *)-1;
	tech->layercount = 0;
	tech->layers = NULL;
	tech->arcprotocount = 0;
	tech->arcprotos = NULL;
	tech->nodeprotocount = 0;
	tech->nodeprotos = NULL;
	return(tech);
}

/*
 * routine to return technology "tech" to the pool of free technologies
 */
void freetechnology(TECHNOLOGY *tech)
{
	REGISTER CLUSTER *clus;
	REGISTER INTBIG i, j;
	REGISTER NODEPROTO *np, *nextnp;
	REGISTER PORTPROTO *pp, *nextpp;
	REGISTER ARCPROTO *ap, *nextap;
	REGISTER TECH_NODES *tn;

	if (tech == NOTECHNOLOGY) return;
	for(np = tech->firstnodeproto; np != NONODEPROTO; np = nextnp)
	{
		nextnp = np->nextnodeproto;
		for(pp = np->firstportproto; pp != NOPORTPROTO; pp = nextpp)
		{
			nextpp = pp->nextportproto;
			efree((char *)pp->protoname);
			freeportproto(pp);
		}
		efree((char *)np->primname);
		freenodeproto(np);
	}
	for(ap = tech->firstarcproto; ap != NOARCPROTO; ap = nextap)
	{
		nextap = ap->nextarcproto;
		efree((char *)ap->protoname);
		db_freearcproto(ap);
	}
	if (tech->numvar != 0) db_freevars(&tech->firstvar, &tech->numvar);
	efree((char *)tech->techname);
	efree((char *)tech->techdescript);
	if ((tech->userbits&STATICTECHNOLOGY) == 0)
	{
		for(i=0; i<tech->layercount; i++)
			efree((char *)tech->layers[i]);
		efree((char *)tech->layers);
		for(i=0; i<tech->arcprotocount; i++)
		{
			efree((char *)tech->arcprotos[i]->arcname);
			efree((char *)tech->arcprotos[i]->list);
			efree((char *)tech->arcprotos[i]);
		}
		efree((char *)tech->arcprotos);
		for(i=0; i<tech->nodeprotocount; i++)
		{
			tn = tech->nodeprotos[i];
			efree((char *)tn->nodename);
			for(j=0; j<tn->portcount; j++)
				efree((char *)tn->portlist[j].protoname);
			efree((char *)tn->portlist);
			if (tn->special != SERPTRANS)
				efree((char *)tn->layerlist); else
			{
				efree((char *)tn->gra);
				efree((char *)tn->ele);
			}
			efree((char *)tn);
		}
		efree((char *)tech->nodeprotos);
	}
	clus = tech->cluster;
	efree((char *)tech);
	freecluster(clus);
}

/*
 * routine to insert technology "tech" into the global linked list and to
 * announce this change to all cached routines.  Returns nonzero on error
 */
INTSML addtechnology(TECHNOLOGY *tech)
{
	REGISTER TECHNOLOGY *t, *lastt;
	REGISTER ROUTINELIST *rl;
	REGISTER INTBIG *newlam;
	REGISTER INTSML i;
	REGISTER LIBRARY *lib;

	/* link it at the end of the list */
	for(t = el_technologies; t != NOTECHNOLOGY; t = t->nexttechnology) lastt = t;
	lastt->nexttechnology = tech;
	tech->nexttechnology = NOTECHNOLOGY;

	/* recount the number of technologies and renumber them */
	el_maxtech = 0;
	for(t = el_technologies; t != NOTECHNOLOGY; t = t->nexttechnology)
		t->techindex = el_maxtech++;

	/* adjust the "lambda" array in all libraries */
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
	{
		newlam = emalloc(((el_maxtech+1) * SIZEOFINTBIG), db_cluster);
		if (newlam == 0)
			return((INTSML)db_error(DBNOMEM|DBADDTECHNOLOGY));
		for(i=0; i<el_maxtech-1; i++) newlam[i] = lib->lambda[i];
		newlam[el_maxtech-1] = tech->deflambda;
		newlam[el_maxtech] = -1;
		efree((char *)lib->lambda);
		lib->lambda = newlam;
	}

	/* announce the change to the number of technologies */
	for(rl = tech_firstcache; rl != NOROUTINELIST; rl = rl->nextroutinelist)
		(*rl->routine)();
	return(0);
}

/*
 * routine to delete technology "tech" from the global list.  Returns
 * nonzero on error.
 */
INTSML killtechnology(TECHNOLOGY *tech)
{
	REGISTER TECHNOLOGY *t, *lastt;
	REGISTER ROUTINELIST *rl;
	REGISTER LIBRARY *lib;
	REGISTER INTBIG *newlam;
	REGISTER INTSML i, j;
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;

	/* cannot kill the generic technology */
	if (tech == gen_tech)
		return((INTSML)db_error(DBLASTECH|DBKILLTECHNOLOGY));

	/* make sure there are no objects from this technology */
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
	{
		for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
		{
			for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
				if (ni->proto->primindex != 0 && ni->proto->tech == tech) break;
			if (ni != NONODEINST) break;
			for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
				if (ai->proto->tech == tech) break;
			if (ai != NOARCINST) break;
		}
		if (np != NONODEPROTO)
			return((INTSML)db_error(DBTECINUSE|DBKILLTECHNOLOGY));
	}

	/* adjust the "lambda" array in all libraries */
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
	{
		newlam = emalloc((el_maxtech * SIZEOFINTBIG), db_cluster);
		if (newlam == 0)
			return((INTSML)db_error(DBNOMEM|DBADDTECHNOLOGY));
		for(i = j = 0, t = el_technologies; t != NOTECHNOLOGY; t = t->nexttechnology, j++)
		{
			if (t == tech) continue;
			newlam[i++] = lib->lambda[j];
		}
		newlam[i] = -1;
		efree((char *)lib->lambda);
		lib->lambda = newlam;
	}

	/* remove "tech" from linked list */
	lastt = NOTECHNOLOGY;
	for(t = el_technologies; t != NOTECHNOLOGY; t = t->nexttechnology)
	{
		if (t == tech) break;
		lastt = t;
	}
	if (lastt == NOTECHNOLOGY) el_technologies = tech->nexttechnology; else
		lastt->nexttechnology = tech->nexttechnology;

	/* deallocate the technology */
	if (tech->numvar != 0) db_freevars(&tech->firstvar, &tech->numvar);
	freetechnology(tech);

	/* recount the number of technologies and renumber them */
	el_maxtech = 0;
	for(t = el_technologies; t != NOTECHNOLOGY; t = t->nexttechnology)
		t->techindex = el_maxtech++;

	/* announce the change to the number of technologies */
	for(rl = tech_firstcache; rl != NOROUTINELIST; rl = rl->nextroutinelist)
		(*rl->routine)();
	return(0);
}

void telltech(TECHNOLOGY *tech, INTSML count, char *par[])
{
	if (tech->setmode == 0) return;
	(*tech->setmode)(count, par);
}

INTBIG asktech(TECHNOLOGY *tech, char *command, ...)
{
	va_list ap;
	INTBIG result;

	if (tech->request == 0) return(0);
	var_start(ap, command);
	result = (*tech->request)(command, ap);
	va_end(ap);
	return(result);
}

/******************** VARIABLE CACHING ********************/

/* this should be called whenever "DRC_min_unconnected_distances" changes!!! */

/*
 * routine to initialize the database variables "DRC_max_distances",
 * "DRC_min_unconnected_distances", "DRC_min_connected_distances", and "DRC_min_width".
 * This is called once at initialization and again whenever the arrays are changed.
 */
void tech_initmaxdrcsurround(void)
{
	REGISTER VARIABLE *var;
	REGISTER TECHNOLOGY *t;
	REGISTER INTSML j, total, l;
	REGISTER INTBIG *dist, m;
	static INTBIG DRC_min_unconnected_distances = 0, DRC_max_distances = 0,
		DRC_min_connected_distances = 0, DRC_min_width = 0;

	if (DRC_max_distances == 0) DRC_max_distances = makekey("DRC_max_distances");
	if (DRC_min_connected_distances == 0)
		DRC_min_connected_distances = makekey("DRC_min_connected_distances");
	if (DRC_min_unconnected_distances == 0)
		DRC_min_unconnected_distances = makekey("DRC_min_unconnected_distances");
	if (DRC_min_width == 0)
		DRC_min_width = makekey("DRC_min_width");

	if (tech_drcmaxdistances != 0) efree((char *)tech_drcmaxdistances);
	if (tech_drcconndistance != 0) efree((char *)tech_drcconndistance);
	if (tech_drcuncondistance != 0) efree((char *)tech_drcuncondistance);
	if (tech_drcminwidth != 0) efree((char *)tech_drcminwidth);

	tech_drcmaxdistances = emalloc((el_maxtech * SIZEOFINTBIG), db_cluster);
	if (tech_drcmaxdistances == 0) return;
	tech_drcconndistance = emalloc((el_maxtech * SIZEOFINTBIG), db_cluster);
	if (tech_drcconndistance == 0) return;
	tech_drcuncondistance = emalloc((el_maxtech * SIZEOFINTBIG), db_cluster);
	if (tech_drcuncondistance == 0) return;
	tech_drcminwidth = emalloc((el_maxtech * SIZEOFINTBIG), db_cluster);
	if (tech_drcminwidth == 0) return;

	for(t = el_technologies; t != NOTECHNOLOGY; t = t->nexttechnology)
	{
		tech_drcmaxdistances[t->techindex] = 0;
		tech_drcconndistance[t->techindex] = 0;
		tech_drcuncondistance[t->techindex] = 0;
		tech_drcminwidth[t->techindex] = 0;

		var = getvalkey((INTBIG)t, VTECHNOLOGY, VFRACT|VISARRAY, DRC_min_width);
		if (var != NOVARIABLE) tech_drcminwidth[t->techindex] = var->addr;

		var = getvalkey((INTBIG)t, VTECHNOLOGY, VFRACT|VISARRAY, DRC_min_connected_distances);
		if (var != NOVARIABLE) tech_drcconndistance[t->techindex] = var->addr;
		var = getvalkey((INTBIG)t, VTECHNOLOGY, VFRACT|VISARRAY, DRC_min_unconnected_distances);
		if (var == NOVARIABLE) continue;
		tech_drcuncondistance[t->techindex] = var->addr;

		total = t->layercount;
		dist = emalloc((total * SIZEOFINTBIG), el_tempcluster);
		if (dist == 0) continue;
		for(l=0; l<total; l++)
		{
			m = XX;
			for(j=0; j<total; j++)
				m = maxi(m, tech_getdrcmindistance(t, l, j, 0));
			dist[l] = m;
		}
		if (setvalkey((INTBIG)t, VTECHNOLOGY, DRC_max_distances, (INTBIG)dist,
			VFRACT|VDONTSAVE|VISARRAY|(total<<VLENGTHSH)) != NOVARIABLE)
		{
			var = getvalkey((INTBIG)t, VTECHNOLOGY, VFRACT|VISARRAY, DRC_max_distances);
			if (var != NOVARIABLE) tech_drcmaxdistances[t->techindex] = var->addr;
		}
		efree((char *)dist);
	}
}

/*
 * routine to initialize the database variable "TECH_node_width_offset".  This
 * is called once at initialization and again whenever the array is changed.
 */
void tech_initnodesizeoffset(void)
{
	REGISTER VARIABLE *var;
	REGISTER TECHNOLOGY *tech;
	static INTBIG node_width_offset = 0;

	/* get the key */
	if (node_width_offset == 0)
		node_width_offset = makekey("TECH_node_width_offset");

	/* free the old cache list if it exists */
	if (tech_node_widoff != 0) efree((char *)tech_node_widoff);

	/* allocate a new cache list */
	tech_node_widoff = emalloc((el_maxtech * SIZEOFINTBIG), db_cluster);
	if (tech_node_widoff == 0) return;

	/* load the cache list */
	for(tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
	{
		var = getvalkey((INTBIG)tech, VTECHNOLOGY, VFRACT|VISARRAY, node_width_offset);
		tech_node_widoff[tech->techindex] = (var == NOVARIABLE ? 0 : var->addr);
	}
}

/*
 * routine to initialize the database variable "TECH_layer_function".  This
 * is called once at initialization and again whenever the array is changed.
 */
void tech_initlayerfunction(void)
{
	REGISTER VARIABLE *var;
	static INTBIG layer_function = 0;
	REGISTER TECHNOLOGY *t;

	if (layer_function == 0) layer_function = makekey("TECH_layer_function");

	if (tech_layer_function != 0) efree((char *)tech_layer_function);

	tech_layer_function = emalloc((el_maxtech * SIZEOFINTBIG), db_cluster);
	if (tech_layer_function == 0) return;

	for(t = el_technologies; t != NOTECHNOLOGY; t = t->nexttechnology)
	{
		var = getvalkey((INTBIG)t, VTECHNOLOGY, VINTEGER|VISARRAY, layer_function);
		tech_layer_function[t->techindex] = (var == NOVARIABLE ? 0 : var->addr);
	}
}

/*
 * routine to initialize the database variable "TECH_layer_names".  This
 * is called once at initialization and again whenever the array is changed.
 */
void tech_initlayername(void)
{
	REGISTER VARIABLE *var;
	static INTBIG layer_names = 0;
	REGISTER TECHNOLOGY *t;

	if (layer_names == 0) layer_names = makekey("TECH_layer_names");

	if (tech_layer_names != 0) efree((char *)tech_layer_names);

	tech_layer_names = emalloc((el_maxtech * SIZEOFINTBIG), db_cluster);
	if (tech_layer_names == 0) return;

	for(t = el_technologies; t != NOTECHNOLOGY; t = t->nexttechnology)
	{
		var = getvalkey((INTBIG)t, VTECHNOLOGY, VSTRING|VISARRAY, layer_names);
		tech_layer_names[t->techindex] = (var == NOVARIABLE ? 0 : var->addr);
	}
}

/*
 * routine to initialize the database variable "TECH_arc_width_offset".  This
 * is called once at initialization and again whenever the array is changed.
 */
void tech_initarcwidthoffset(void)
{
	REGISTER VARIABLE *var;
	static INTBIG arc_width_offset = 0;
	REGISTER TECHNOLOGY *tech;

	if (arc_width_offset == 0) arc_width_offset = makekey("TECH_arc_width_offset");

	if (tech_arc_widoff != 0) efree((char *)tech_arc_widoff);

	tech_arc_widoff = emalloc((el_maxtech * SIZEOFINTBIG), db_cluster);
	if (tech_arc_widoff == 0) return;

	for(tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
	{
		var = getvalkey((INTBIG)tech, VTECHNOLOGY, VFRACT|VISARRAY, arc_width_offset);
		tech_arc_widoff[tech->techindex] = (var == NOVARIABLE ? 0 : var->addr);
	}
}

/*
 * routine to register function "proc" in a list that will be invoked
 * whenever the number of technologies changes
 */
void registertechnologycache(void (*proc)(void))
{
	REGISTER ROUTINELIST *rl;

	/* allocate a ROUTINELIST object */
	rl = (ROUTINELIST *)emalloc(sizeof (ROUTINELIST), db_cluster);
	if (rl == 0) return;

	/* put this object at the start */
	rl->nextroutinelist = tech_firstcache;
	tech_firstcache = rl;

	/* insert the data and run the routine */
	rl->routine = proc;
	(*proc)();
}

/*
 * routine called once at initialization to register the database
 * functions that cache technology-related variables
 */
void db_inittechcache(void)
{
	registertechnologycache(tech_initmaxdrcsurround);
	registertechnologycache(tech_initnodesizeoffset);
	registertechnologycache(tech_initlayerfunction);
	registertechnologycache(tech_initlayername);
	registertechnologycache(tech_initarcwidthoffset);
}

/******************** NODEINST DESCRIPTION ********************/

/*
 * routine to report the number of distinct graphical polygons used to compose
 * primitive nodeinst "ni".
 */
INTSML nodepolys(NODEINST *ni)
{
	REGISTER INTSML pindex, count;
	TECH_NODES *thistn;
	REGISTER NODEPROTO *np;

	np = ni->proto;
	pindex = np->primindex;
	if (pindex == 0) return(0);

	/* if the technology has its own routine, use it */
	if (np->tech->nodepolys != 0)
		return((*(np->tech->nodepolys))(ni));

	thistn = np->tech->nodeprotos[pindex-1];
	count = thistn->layercount;
	switch (thistn->special)
	{
		case MULTICUT:
			count += tech_moscutcount(ni, thistn->f1, thistn->f2, thistn->f3, thistn->f4) - 1;
			break;
		case SERPTRANS:
			count = tech_inittrans(count, ni);
			break;
	}

	/* zero the count if this node is not to be displayed */
	if ((ni->userbits&WIPED) != 0) count = 0; else
	{
		/* zero the count if this node erases when connected to 1 or 2 arcs */
		if ((np->userbits&WIPEON1OR2) != 0)
		{
			if (tech_pinusecount(ni, NOARCPROTO) != 0) count = 0;
		}
	}

	/* add in displayable variables */
	tech_realpolys = count;
	count += tech_displayablenvars(ni);
	return(count);
}

/*
 * routine to report the number of distinct electrical polygons used to
 * compose primitive nodeinst "ni".
 */
INTSML nodeEpolys(NODEINST *ni)
{
	REGISTER INTSML pindex, count;
	REGISTER TECH_NODES *thistn;

	pindex = ni->proto->primindex;
	if (pindex == 0) return(0);

	/* if the technology has its own routine, use it */
	if (ni->proto->tech->nodeEpolys != 0)
		return((*(ni->proto->tech->nodeEpolys))(ni));

	thistn = ni->proto->tech->nodeprotos[pindex-1];
	count = thistn->layercount;
	switch (thistn->special)
	{
		case MULTICUT:
			count += tech_moscutcount(ni, thistn->f1, thistn->f2, thistn->f3, thistn->f4) - 1;
			break;
		case SERPTRANS:
			count = tech_inittrans((INTSML)thistn->f1, ni);
			break;
	}
	return(count);
}

/*
 * routine to report the shape of graphical polygon "box" of primitive
 * nodeinst "ni".  The polygon is returned in "poly".
 */
void shapenodepoly(NODEINST *ni, INTSML box, POLYGON *poly)
{
	TECH_POLYGON *lay;
	REGISTER TECH_PORTS *p;
	REGISTER INTSML pindex, count;
	REGISTER NODEPROTO *np;
	REGISTER INTBIG lambda;
	REGISTER TECH_NODES *thistn;

	np = ni->proto;
	pindex = np->primindex;
	if (pindex == 0) return;
	poly->tech = np->tech;

	/* if the technology has its own routine, use it */
	if (np->tech->shapenodepoly != 0)
	{
		(*(np->tech->shapenodepoly))(ni, box, poly);
		return;
	}

	/* handle displayable variables */
	if (box >= tech_realpolys)
	{
		(void)tech_filldisplayablenvar(ni, poly);
		return;
	}

	thistn = np->tech->nodeprotos[pindex-1];
	lambda = np->tech->deflambda;
	switch (thistn->special)
	{
		case SERPTRANS:
			if (box > 1 || (ni->userbits&NSHORT) == 0) p = (TECH_PORTS *)0; else
				p = thistn->portlist;
			tech_filltrans(poly, &lay, thistn->gra, ni, lambda, box, p);
			break;

		case MULTICUT:
			count = thistn->layercount - 1;
			if (box >= count)
			{
				lay = &thistn->layerlist[count];
				tech_moscutpoly(ni, (INTSML)(box-count), lay->points);
				tech_fillpoly(poly, lay, ni, lambda, FILLED);
				break;
			}

		default:
			lay = &thistn->layerlist[box];
			tech_fillpoly(poly, lay, ni, lambda, FILLED);
			break;
	}

	poly->desc = np->tech->layers[poly->layer];
}

/*
 * routine to report the shape of electrical polygon "box" of primitive
 * nodeinst "ni".  The polygon is returned in "poly".
 */
void shapeEnodepoly(NODEINST *ni, INTSML box, POLYGON *poly)
{
	TECH_POLYGON *lay;
	REGISTER INTSML pindex, count;
	REGISTER NODEPROTO *np;
	REGISTER INTBIG lambda;
	REGISTER TECH_NODES *thistn;

	np = ni->proto;
	pindex = np->primindex;
	if (pindex == 0) return;
	poly->tech = np->tech;

	/* if the technology has its own routine, use it */
	if (np->tech->shapeEnodepoly != 0)
	{
		(*(np->tech->shapeEnodepoly))(ni, box, poly);
		return;
	}

	thistn = np->tech->nodeprotos[pindex-1];
	lambda = np->tech->deflambda;
	switch (thistn->special)
	{
		case MOSTRANS:
			lay = &((TECH_POLYGON *)thistn->ele)[box];
			tech_fillpoly(poly, lay, ni, lambda, FILLED);
			break;

		case SERPTRANS:
			tech_filltrans(poly, &lay, thistn->ele, ni, lambda, box, (TECH_PORTS *)0);
			break;

		case MULTICUT:
			count = thistn->layercount - 1;
			if (box >= count)
			{
				lay = &thistn->layerlist[count];
				tech_moscutpoly(ni, (INTSML)(box-count), lay->points);
				tech_fillpoly(poly, lay, ni, lambda, FILLED);
				break;
			}

		default:
			lay = &thistn->layerlist[box];
			tech_fillpoly(poly, lay, ni, lambda, FILLED);
			break;
	}

	/* handle port prototype association */
	if (lay->portnum < 0) poly->portproto = NOPORTPROTO; else
		poly->portproto = thistn->portlist[lay->portnum].addr;

	poly->desc = np->tech->layers[poly->layer];
}

/*
 * routine to report the acutal size offsets of nodeinst "ni".
 * This is not always obvious since the extent of a nodeinst is not
 * necessarily its size.  This routine accesses the "node_width_offset"
 * variable on the technology objects.
 */
void nodesizeoffset(NODEINST *ni, INTBIG *lx, INTBIG *ly, INTBIG *hx, INTBIG *hy)
{
	REGISTER NODEPROTO *np;

	np = ni->proto;
	if (np->primindex == 0) { *lx = *ly = *hx = *hy = 0;   return; }

	/* if the technology has its own routine, use it */
	if (np->tech->nodesizeoffset != 0)
	{
		(*(np->tech->nodesizeoffset))(ni, lx, ly, hx, hy);
		return;
	}

	/* use value from prototype */
	nodeprotosizeoffset(ni->proto, lx, ly, hx, hy);
}

/*
 * routine to report the acutal size offsets of nodeinst "ni".
 * This is not always obvious since the extent of a nodeinst is not
 * necessarily its size.  This routine accesses the "node_width_offset"
 * variable on the technology objects.
 */
void nodeprotosizeoffset(NODEPROTO *np, INTBIG *lx, INTBIG *ly, INTBIG *hx, INTBIG *hy)
{
	REGISTER INTBIG *base, addr;

	*lx = *ly = *hx = *hy = 0;
	if (np->primindex == 0) return;

	/* make sure cache of information is valid */
	if (tech_node_widoff == 0)
	{
		tech_initnodesizeoffset();
		if (tech_node_widoff == 0) return;
	}

	addr = tech_node_widoff[np->tech->techindex];
	if (addr == 0) return;

	base = &((INTBIG *)addr)[(np->primindex-1)*4];
	*lx = *base++ * np->tech->deflambda/WHOLE;
	*hx = *base++ * np->tech->deflambda/WHOLE;
	*ly = *base++ * np->tech->deflambda/WHOLE;
	*hy = *base * np->tech->deflambda/WHOLE;
}

/*
 * routine to return the node prototype behavior index for nodeinst "ni".
 * Any extra information about the instance is returned in the string
 * pointer "extra" (which is set to zero if there is no other information)
 * Jan. 1991 SRP: In case of generic transistors, return the extra string
 * anyhow, even if it does not contain a type declaration.
 */
INTSML nodefunction(NODEINST *ni, char **extra)
{
	REGISTER INTSML type;
	REGISTER VARIABLE *var;
	REGISTER char *str;
	static INTBIG SCHEME_array_func = 0;

	if (ni->proto->primindex == 0) return(NPUNKNOWN);
	type = (INTSML)((ni->proto->userbits&NFUNCTION) >> NFUNCTIONSH);
	*extra = 0;
	if (type == NPRESIST)
	{
		var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, sch_resistancekey);
		if (var != NOVARIABLE) *extra = (char *)var->addr;
	}
	if (type == NPCAPAC)
	{
		var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, sch_capacitancekey);
		if (var != NOVARIABLE)
		{
			str = (char *)var->addr;
			while (*str == ' ' || *str == '\t') str++;
			if (*str == 'e' || *str == 'E')
			{
				str++;
				type = NPECAPAC;
			}
			*extra = str;
		}
	}
	if (type == NPDIODE)
	{
		var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, sch_diodekey);
		if (var != NOVARIABLE)
		{
			str = (char *)var->addr;
			while (*str == ' ' || *str == '\t') str++;
			if (*str == 'z' || *str == 'Z')
			{
				str++;
				type = NPDIODEZ;
			}
			*extra = str;
		}
	}
	if (type == NPINDUCT)
	{
		var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, sch_inductancekey);
		if (var != NOVARIABLE) *extra = (char *)var->addr;
	}
	if (type == NPTRANS)
	{
		/* undefined transistor: look at its transistor type */
		var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, sch_transistortypekey);
		if (var != NOVARIABLE)
		{
			str = (char *)var->addr;
			if (namesamen(str, "nmos", 4) == 0)
			{
				if (str[4] != 0) *extra = &str[4];
				return(NPTRANMOS);
			}
			if (namesamen(str, "dmos", 4) == 0)
			{
				if (str[4] != 0) *extra = &str[4];
				return(NPTRADMOS);
			}
			if (namesamen(str, "pmos", 4) == 0)
			{
				if (str[4] != 0) *extra = &str[4];
				return(NPTRAPMOS);
			}
			if (namesamen(str, "npn", 3) == 0)
			{
				if (str[3] != 0) *extra = &str[3];
				return(NPTRANPN);
			}
			if (namesamen(str, "pnp", 3) == 0)
			{
				if (str[3] != 0) *extra = &str[3];
				return(NPTRAPNP);
			}
			if (namesamen(str, "njfet", 5) == 0)
			{
				if (str[5] != 0) *extra = &str[5];
				return(NPTRANJFET);
			}
			if (namesamen(str, "pjfet", 5) == 0)
			{
				if (str[5] != 0) *extra = &str[5];
				return(NPTRAPJFET);
			}
			if (namesamen(str, "dmes", 4) == 0)
			{
				if (str[4] != 0) *extra = &str[4];
				return(NPTRADMES);
			}
			if (namesamen(str, "emes", 4) == 0)
			{
				if (str[4] != 0) *extra = &str[4];
				return(NPTRAEMES);
			}
			if (namesamen(str, "e2l", 4) == 0)
			{
				if (str[4] != 0) *extra = &str[4];
				return(NPTRANE2L);
			}
			if (str[0] != 0) *extra = str;    /* default */
			return(NPTRANS);
		}
	}

	/* fixed array device: look for more information */
	if (type == NPARRAY)
	{
		if (SCHEME_array_func == 0) SCHEME_array_func = makekey("SCHEM_array_function");
		var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, SCHEME_array_func);
		if (var != NOVARIABLE)
		{
			str = (char *)var->addr;
			if (str[0]) *extra = str;
		}
	}

	/* source node: look for more information */
	if (type == NPSOURCE)
	{
		var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, sch_sourcekey);
		if (var != NOVARIABLE)
		{
			str = (char *)var->addr;
			if (namesamen(str, "vd", 2) == 0)
			{
				if (str[2] == '/') *extra = &str[3];
				return(NPSOURCEDC);
			}
			if (namesamen(str, "v", 1) == 0)
			{
				if (str[1] == '/') *extra = &str[2];
				return(NPSOURCEV);
			}
			if (namesamen(str, "cm", 2) == 0)
			{
				if (str[2] == '/') *extra = &str[3];
				return(NPSOURCECM);
			}
			if (namesamen(str, "c", 1) == 0)
			{
				if (str[1] == '/') *extra = &str[2];
				return(NPSOURCEC);
			}
			if (namesamen(str, "t", 1) == 0)
			{
				if (str[1] == '/') *extra = &str[2];
				return(NPSOURCET);
			}
			if (namesamen(str, "a", 1) == 0)
			{
				if (str[1] == '/') *extra = &str[2];
				return(NPSOURCEAC);
			}
			if (namesamen(str, "n", 1) == 0)
			{
				if (str[1] == '/') *extra = &str[2];
				return(NPSOURCEN);
			}
			if (namesamen(str, "x", 1) == 0)
			{
				if (str[1] == '/') *extra = &str[2];
				return(NPSOURCEX);
			}
			if (namesamen(str, "b", 1) == 0)
			{
				if (str[1] == '/') *extra = &str[2];
				return(NPSOURCEB);
			}
			if (namesamen(str, "s", 1) == 0)
			{
				if (str[1] == '/') *extra = &str[2];
				return(NPSOURCES);
			}
			if (str[0] != 0) *extra = str;    /* default */
			return(NPTRANS);
		}
	}

	/* two-port device: look for more information */
	if (type == NPTLINE)
	{
		var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, sch_twoportkey);
		if (var != NOVARIABLE)
		{
			str = (char *)var->addr;
			while (*str == ' ') str++;
			switch (*str)
			{
				case 'u':  case 'U':
					if (str[1] != 0) *extra = &str[1];
					return(NPVCVS);
				case 'g':  case 'G':
					if (str[1] != 0) *extra = &str[1];
					return(NPVCCS);
				case 'h':  case 'H':
					if (str[1] != 0) *extra = &str[1];
					return(NPCCVS);
				case 'f':  case 'F':
					if (str[1] != 0) *extra = &str[1];
					return(NPCCCS);
				case 'l':  case 'L':
					if (str[1] != 0) *extra = &str[1];
					return(NPTLINE);
			}
			if (*str) *extra = str;
		}
	}
	return(type);
}

/*
 * routine to return the name of node "ni" with function "fun"
 */
char *nodefunctionname(INTSML fun, NODEINST *ni)
{
	/* these must match the "define"s in "efunction.h" */
	static char *nodefunname[] =
	{
		"unknown",						/* NPUNKNOWN */
		"pin",							/* NPPIN */
		"contact",						/* NPCONTACT */
		"pure-layer-node",				/* NPNODE */
		"connection",					/* NPCONNECT */
		"nMOS-transistor",				/* NPTRANMOS */
		"DMOS-transistor",				/* NPTRADMOS */
		"PMOS-transistor",				/* NPTRAPMOS */
		"NPN-transistor",				/* NPTRANPN */
		"PNP-transistor",				/* NPTRAPNP */
		"n-type-JFET-transistor",		/* NPTRANJFET */
		"p-type-JFET-transistor",		/* NPTRAPJFET */
		"depletion-mesfet",				/* NPTRADMES */
		"enhancement-mesfet",			/* NPTRAEMES */
		"dual-emitter-transistor",		/* NPTRANE2L */
		"prototype-defined-transistor",	/* NPTRANSREF */
		"transistor",					/* NPTRANS */
		"resistor",						/* NPRESIST */
		"capacitor",					/* NPCAPAC */
		"electrolytic-capacitor",		/* NPECAPAC */
		"diode",						/* NPDIODE */
		"zener-diode",					/* NPDIODEZ */
		"inductor",						/* NPINDUCT */
		"meter",						/* NPMETER */
		"base",							/* NPBASE */
		"emitter",						/* NPEMIT */
		"collector",					/* NPCOLLECT */
		"buffer",						/* NPBUFFER */
		"AND-gate",						/* NPGATEAND */
		"OR-gate",						/* NPGATEOR */
		"XOR-gate",						/* NPGATEXOR */
		"flip-flop",					/* NPFLIPFLOP */
		"multiplexor",					/* NPMUX */
		"power",						/* NPCONPOWER */
		"ground",						/* NPCONGROUND */
		"source",						/* NPSOURCE */
		"voltage-source",				/* NPSOURCEV */
		"current-source",				/* NPSOURCEC */
		"current-meter",				/* NPSOURCECM */
		"transient-analysis",			/* NPSOURCET */
		"DC-analysis",					/* NPSOURCEDC */
		"AC-analysis",					/* NPSOURCEAC */
		"nodeset-source",				/* NPSOURCEN */
		"extension-source",				/* NPSOURCEX */
		"bulk-source",					/* NPSOURCEB */
		"special-source",				/* NPSOURCES */
		"substrate",					/* NPSUBSTRATE */
		"well",							/* NPWELL */
		"artwork",						/* NPART */
		"array",						/* NPARRAY */
		"align",						/* NPALIGN */
		"ccvs",							/* NPCCVS */
		"cccs",							/* NPCCCS */
		"vcvs",							/* NPVCVS */
		"vccs",							/* NPVCCS */
		"transmission-line"				/* NPTLINE */
	};

	if (fun == NPTRANSREF && ni != NONODEINST)
	{
		(void)initinfstr();
		(void)addstringtoinfstr("Transistor-");
		(void)addstringtoinfstr(ni->proto->primname);
		return(returninfstr());
	}
	if (fun < 0 || fun > NPTLINE) return("");
	return(nodefunname[fun]);
}

/*
 * routine to tell whether geometry module "pos" points to a field-effect
 * transtor.  Returns nonzero if so.
 */
INTSML isfet(GEOM *pos)
{
	REGISTER INTSML fun;
	char *extra;

	if (pos->entrytype != OBJNODEINST) return(0);
	fun = nodefunction(pos->entryaddr.ni, &extra);
	switch (fun)
	{
		case NPTRANMOS:
		case NPTRADMOS:
		case NPTRAPMOS:
		case NPTRADMES:
		case NPTRAEMES:
			return(1);
	}
	return(0);
}

/*
 * routine to determine the length and width of the primitive transistor
 * node "ni" and return it in the reference integers "length" and "width".
 * The value returned is in internal units.
 * If the value cannot be determined, -1 is returned in the length and width.
 * Mar. 1991 SRP: If the first character of *extra is not a digit or a
 * sign, then do not call latoa.  This allows us to put a model name after
 * the type string 'npn', etc. in SCHEM_transistortype that does not include
 * any size data.
 */
void transistorsize(NODEINST *ni, INTBIG *length, INTBIG *width)
{
	INTBIG lx, ly, hx, hy;
	REGISTER INTSML count, i;
	REGISTER INTBIG fx,fy, tx,ty;
	char *extra;
	REGISTER VARIABLE *var;

	*length = *width = -1;
	switch ((ni->proto->userbits&NFUNCTION) >> NFUNCTIONSH)
	{
		case NPTRAEMES:
		case NPTRADMES:
		case NPTRANMOS:
		case NPTRADMOS:
		case NPTRAPMOS:
			var = gettrace(ni);
			if (var != NOVARIABLE)
			{
				/* serpentine transistor: compute path length */
				*width = 0;
				count = getlength(var) / 2;
				for(i=1; i<count; i++)
				{
					fx = ((INTBIG *)var->addr)[i*2-2];
					fy = ((INTBIG *)var->addr)[i*2-1];
					tx = ((INTBIG *)var->addr)[i*2];
					ty = ((INTBIG *)var->addr)[i*2+1];
					*width += computedistance(fx,fy, tx,ty);
				}

				var = getvalkey((INTBIG)ni, VNODEINST, VFRACT, el_transistor_width);
				if (var != NOVARIABLE) *length = var->addr * ni->proto->tech->deflambda/WHOLE; else
				{
					nodesizeoffset(ni, &lx, &ly, &hx, &hy);
					*length = ni->proto->highy-hy - (ni->proto->lowy+ly);
				}
			} else
			{
				/* normal transistor: subtract offset for active area */
				nodesizeoffset(ni, &lx, &ly, &hx, &hy);
				*length = ni->highy-hy - (ni->lowy+ly);
				*width = ni->highx-hx - (ni->lowx+lx);
			}
			break;

		case NPTRANS:
			(void)nodefunction(ni, &extra);
			if (extra != 0 && (*extra == '-' || *extra == '+' || isdigit(*extra)))
			{
				*width = atola(extra);
				while (*extra != '/' && *extra != 0) extra++;
				if (*extra != '/') *length = 1; else *length = atola(&extra[1]);
			}
			break;
	}
}

/*
 * routine to return the ports of transistor "ni" in "gateleft", "gateright",
 * "activetop", and "activebottom".  If "gateright" is NOPORTPROTO, there is only
 * one gate port.
 *
 * This code is predicated upon the fact that all MOS transistors have four ports
 * in the same sequence: gateleft, activetop, gateright, activebottom.  The schematic
 * transistor, which has only three ports, is ordered: gate, source, drain.
 * We have to allow for multiple ported transistors, so we will look at the
 * nodefunction again (SRP)
 */
void transistorports(NODEINST *ni, PORTPROTO **gateleft, PORTPROTO **gateright,
	PORTPROTO **activetop, PORTPROTO **activebottom)
{
	char *dummy;

	*activetop = *gateright = *activebottom = NOPORTPROTO;
	*gateleft = ni->proto->firstportproto;
	if (*gateleft == NOPORTPROTO) return;
	*activetop = (*gateleft)->nextportproto;
	if (*activetop == NOPORTPROTO) return;
	*gateright = (*activetop)->nextportproto;
	if ((*gateright)->nextportproto == NOPORTPROTO || nodefunction(ni, &dummy) == NPTRANPN ||
		nodefunction(ni, &dummy) == NPTRAPNP )
	{
		*activebottom = *gateright;
		*gateright = NOPORTPROTO;
	} else
		*activebottom = (*gateright)->nextportproto;
}

/*
 * routine to get the starting and ending angle of the arc described by node "ni".
 * Sets "startoffset" to the fractional difference between the node rotation and the
 * true starting angle of the arc (this will be less than a tenth of a degree, since
 * node rotation is in tenths of a degree).  Sets "endangle" to the ending rotation
 * of the arc (the true ending angle is this plus the node rotation and "startoffset").
 * Both "startoffset" and "endangle" are in radians).
 * If the node is not circular, both values are set to zero.
 */
void getarcdegrees(NODEINST *ni, double *startoffset, double *endangle)
{
	REGISTER VARIABLE *var;
	float sof, eaf;

	*startoffset = *endangle = 0.0;
	if (ni->proto != art_circleprim) return;
	var = getvalkey((INTBIG)ni, VNODEINST, -1, art_degreeskey);
	if (var == NOVARIABLE) return;
	if ((var->type&VTYPE) == VINTEGER)
	{
		*startoffset = 0.0;
		*endangle = (double)var->addr * EPI / 1800.0;
		return;
	}
	if ((var->type&(VTYPE|VISARRAY)) == (VFLOAT|VISARRAY))
	{
		sof = ((float *)var->addr)[0];
		eaf = ((float *)var->addr)[1];
		*startoffset = (double)sof;
		*endangle = (double)eaf;
	}
}

/*
 * routine to set the starting and ending angle of the arc described by node "ni".
 * Sets "startoffset" to the fractional difference between the node rotation and the
 * true starting angle of the arc (this will be less than a tenth of a degree, since
 * node rotation is in tenths of a degree).  Sets "endangle" to the ending rotation
 * of the arc (the true ending angle is this plus the node rotation and "startoffset").
 * Both "startoffset" and "endangle" are in radians).
 * If the node is not circular, this call does nothing.
 */
void setarcdegrees(NODEINST *ni, double startoffset, double endangle)
{
	REGISTER INTBIG angle;
	REGISTER double rangle;
	float degs[2];

	if (ni->proto != art_circleprim) return;
	if (startoffset == 0.0 && endangle == 0.0)
	{
		/* no arc on this circle: remove any data */
		if (getvalkey((INTBIG)ni, VNODEINST, -1, art_degreeskey) == NOVARIABLE) return;
		delvalkey((INTBIG)ni, VNODEINST, art_degreeskey);
	} else
	{
		/* put arc information on the circle */
		angle = rounddouble(endangle * 1800.0 / EPI);
		rangle = (double)angle * EPI / 1800.0;
		if (startoffset == 0.0 && rangle == endangle)
		{
			(void)setvalkey((INTBIG)ni, VNODEINST, art_degreeskey, angle, VINTEGER);
		} else
		{
			degs[0] = (float)startoffset;
			degs[1] = (float)endangle;
			(void)setvalkey((INTBIG)ni, VNODEINST, art_degreeskey, (INTBIG)degs,
				VFLOAT|VISARRAY|(2<<VLENGTHSH));
		}
	}
	updategeom(ni->geom, ni->parent);
	db_setchangefacet(ni->parent);
}

/*
 * Routine to return the endpoints of the arc on node "ni" that has a starting offset of
 * "startoffset" and an ending angle of "endangle" (from "getarcdegrees()" above).  Returns
 * the coordinates in (fx,fy) and (tx,ty).
 */
void getarcendpoints(NODEINST *ni, double startoffset, double endangle, INTBIG *fx, INTBIG *fy,
	INTBIG *tx, INTBIG *ty)
{
	REGISTER INTBIG cx, cy, radius;

	cx = (ni->lowx + ni->highx) / 2;
	cy = (ni->lowy + ni->highy) / 2;
	radius = (ni->highx - ni->lowx) / 2;
	startoffset += ((double)ni->rotation) * EPI / 1800.0;
	if (ni->transpose != 0)
	{
		startoffset = 1.5 * EPI - startoffset - endangle;
		if (startoffset < 0.0) startoffset += EPI * 2.0;
	}
	*fx = cx + rounddouble(cos(startoffset) * radius);
	*fy = cy + rounddouble(sin(startoffset) * radius);
	*tx = cx + rounddouble(cos(startoffset+endangle) * radius);
	*ty = cy + rounddouble(sin(startoffset+endangle) * radius);
}

/*
 * Routine to get the primitive node in technology "tech" that is the pure-layer node
 * for layer "layer" (if layer is negative, then look for the pure-layer node with
 * function "function").  Returns NONODEPROTO if none is found.
 */
NODEPROTO *getpurelayernode(TECHNOLOGY *tech, INTBIG layer, INTBIG function)
{
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ni;
	REGISTER INTSML i;
	REGISTER INTBIG fun;
	static POLYGON *poly = NOPOLYGON;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, db_cluster);

	ni = dummynode();
	ni->lowx = ni->highx = 0;
	ni->lowy = ni->highy = 0;
	for(np = tech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
	{
		if (((np->userbits&NFUNCTION) >> NFUNCTIONSH) != NPNODE) continue;
		ni->proto = np;
		i = nodepolys(ni);
		if (i != 1) continue;
		shapenodepoly(ni, 0, poly);
		if (layer >= 0)
		{
			if (poly->layer == layer) return(np);
		} else
		{
			fun = layerfunction(tech, poly->layer);
			if ((fun&LFTYPE) == function) return(np);
		}
	}
	return(NONODEPROTO);
}

/******************** PORT DESCRIPTION ********************/

/*
 * routine to set polygon "poly" to the shape of port "pp" on nodeinst "ni".
 * If "purpose" is zero, the entire port is desired.  If "purpose" is 1,
 * the exact location of a new port is desired and that port should be
 * optimally close to the co-ordinates in (poly->xv[0],poly->yv[0]).
 */
void shapeportpoly(NODEINST *ni, PORTPROTO *pp, POLYGON *poly, INTSML purpose)
{
	REGISTER INTSML pindex;
	REGISTER TECH_NODES *thistn;
	XARRAY localtran, tempt1, tempt2, *t1, *t2, *swapt;

	/* look down to the bottom level node/port */
	t1 = (XARRAY *)tempt1;   t2 = (XARRAY *)tempt2;
	if (ni->rotation == 0 && ni->transpose == 0) transid(*t1); else
		makerot(ni, *t1);
	while (ni->proto->primindex == 0)
	{
		maketrans(ni, localtran);
		transmult(localtran, *t1, *t2);
		swapt = t1;   t1 = t2;   t2 = swapt;
		ni = pp->subnodeinst;
		pp = pp->subportproto;
		if (ni->rotation != 0 || ni->transpose != 0)
		{
			makerot(ni, localtran);
			transmult(localtran, *t1, *t2);
			swapt = t1;   t1 = t2;   t2 = swapt;
		}
	}

	/* if the technology has its own routine, use it */
	if (ni->proto->tech->shapeportpoly != 0)
	{
		(*(ni->proto->tech->shapeportpoly))(ni, pp, poly, *t1, purpose);
		return;
	}

	pindex = ni->proto->primindex;
	thistn = ni->proto->tech->nodeprotos[pindex-1];
	switch (thistn->special)
	{
		case SERPTRANS:
			tech_filltransport(ni, pp, poly, *t1, thistn, thistn->f2, thistn->f3,
				thistn->f4, thistn->f5, thistn->f6);
			break;

		default:
			tech_fillportpoly(ni, pp, poly, *t1, thistn, CLOSED);
			break;
	}
}

/*
 * routine to set polygon "poly" to the shape of port "pp" on nodeinst "ni",
 * given that the node transformation is already known and is "trans".
 */
void shapetransportpoly(NODEINST *ni, PORTPROTO *pp, POLYGON *poly, XARRAY trans)
{
	REGISTER INTSML pindex;
	REGISTER TECH_NODES *thistn;

	/* if the technology has its own routine, use it */
	if (ni->proto->tech->shapeportpoly != 0)
	{
		(*(ni->proto->tech->shapeportpoly))(ni, pp, poly, trans, 0);
		return;
	}

	pindex = ni->proto->primindex;
	thistn = ni->proto->tech->nodeprotos[pindex-1];
	switch (thistn->special)
	{
		case SERPTRANS:
			tech_filltransport(ni, pp, poly, trans, thistn, thistn->f2, thistn->f3,
				thistn->f4, thistn->f5, thistn->f6);
			break;

		default:
			tech_fillportpoly(ni, pp, poly, trans, thistn, CLOSED);
			break;
	}
}

/*
 * routine to compute the center of port "pp" in nodeinst "ni" (taking
 * nodeinst position, transposition and rotation into account).  The location
 * is placed in the reference integer parameters "x" and "y"
 */
void portposition(NODEINST *ni, PORTPROTO *pp, INTBIG *x, INTBIG *y)
{
	static POLYGON *poly = NOPOLYGON;

	/* make sure there is a polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, db_cluster);

	/* get the polygon describing the port */
	shapeportpoly(ni, pp, poly, 0);

	/* determine the center of the polygon */
	getcenter(poly, x, y);
}

/*
 * routine to return nonzero if port "pp" is a power port
 */
INTSML portispower(PORTPROTO *pp)
{
	if ((pp->userbits&STATEBITS) == PWRPORT) return(1);
	if ((pp->userbits&STATEBITS) != 0) return(0);
	if (namesamen(pp->protoname, "vdd", 3) == 0) return(1);
	if (namesamen(pp->protoname, "pwr", 3) == 0) return(1);
	if (namesamen(pp->protoname, "power", 5) == 0) return(1);
	if (namesamen(pp->protoname, "vcc", 3) == 0) return(1);
	return(0);
}

/*
 * routine to return nonzero if port "pp" is a ground port
 */
INTSML portisground(PORTPROTO *pp)
{
	if ((pp->userbits&STATEBITS) == GNDPORT) return(1);
	if ((pp->userbits&STATEBITS) != 0) return(0);
	if (namesamen(pp->protoname, "gnd", 3) == 0) return(1);
	if (namesamen(pp->protoname, "ground", 6) == 0) return(1);
	if (namesamen(pp->protoname, "vss", 3) == 0) return(1);
	return(0);
}

/******************** ARCINST DESCRIPTION ********************/

/*
 * routine to report the number of distinct polygons used to compose
 * primitive arcinst "ai".
 */
INTSML arcpolys(ARCINST *ai)
{
	REGISTER INTSML i;
	REGISTER TECHNOLOGY *tech;

	/* if the technology has its own routine, use it */
	tech = ai->proto->tech;
	if (tech->arcpolys != 0) return((*(tech->arcpolys))(ai));

	/* reset negated bit if set and now allowed */
	if ((tech->userbits&NONEGATEDARCS) != 0 && (ai->userbits&ISNEGATED) != 0)
		tech_resetnegated(ai);

	/* get number of polygons in the arc */
	i = tech->arcprotos[ai->proto->arcindex]->laycount;

	/* add one layer if arc is directional and technology allows it */
	if ((tech->userbits&NODIRECTIONALARCS) == 0 && (ai->userbits&ISDIRECTIONAL) != 0) i++;

	/* add in displayable variables */
	tech_realpolys = i;
	i += tech_displayableavars(ai);
	return(i);
}

/*
 * routine to describe polygon number "box" of arcinst "ai".  The description
 * is placed in the polygon "poly".
 */
void shapearcpoly(ARCINST *ai, INTSML box, POLYGON *poly)
{
	REGISTER INTSML pindex;
	REGISTER ARCPROTO *ap;
	REGISTER TECHNOLOGY *tech;
	REGISTER TECH_ARCLAY *thista;

	/* if the technology has its own routine, use it */
	ap = ai->proto;
	tech = ap->tech;
	poly->tech = tech;
	if (tech->shapearcpoly != 0)
	{
		(*(tech->shapearcpoly))(ai, box, poly);
		return;
	}

	/* handle displayable variables */
	if (box >= tech_realpolys)
	{
		(void)tech_filldisplayableavar(ai, poly);
		return;
	}

	pindex = ap->arcindex;
	if (box >= tech->arcprotos[pindex]->laycount) tech_makearrow(ai, poly); else
	{
		thista = &tech->arcprotos[pindex]->list[box];
		makearcpoly(ai->length, ai->width-thista->off*tech->deflambda/WHOLE, ai, poly,
			thista->style);
		poly->layer = thista->lay;
		poly->desc = tech->layers[poly->layer];
	}
}

/*
 * routine to fill polygon "poly" with the outline of the curved arc in
 * "ai" whose width is "wid".  The style of the polygon is set to "style".
 * If there is no curvature information in the arc, the routine returns -1,
 * otherwise it returns 0.
 */
INTSML curvedarcoutline(ARCINST *ai, POLYGON *poly, INTSML style, INTBIG wid)
{
	REGISTER INTSML i, points, anglebase, anglerange, pieces, a;
	REGISTER INTBIG radius, centerx, centery, innerradius, outerradius, sin, cos;
	INTBIG x1, y1, x2, y2;
	REGISTER VARIABLE *var;

	/* get the radius information on the arc */
	var = getvalkey((INTBIG)ai, VARCINST, VINTEGER, el_arc_radius);
	if (var == NOVARIABLE) return(-1);
	radius = var->addr;

	/* see if the radius can work with these arc ends */
	if (abs(radius)*2 < ai->length) return(-1);

	/* determine the center of the circle */
	if (findcenters(abs(radius), ai->end[0].xpos, ai->end[0].ypos,
		ai->end[1].xpos, ai->end[1].ypos, ai->length, &x1,&y1, &x2,&y2) != 0) return(-1);

	if (radius < 0)
	{
		radius = -radius;
		centerx = x1;   centery = y1;
	} else
	{
		centerx = x2;   centery = y2;
	}

	/* determine the base and range of angles */
	anglebase = figureangle(centerx,centery, ai->end[0].xpos,ai->end[0].ypos);
	anglerange = figureangle(centerx, centery, ai->end[1].xpos, ai->end[1].ypos);
	if ((ai->userbits&REVERSEEND) != 0)
	{
		i = anglebase;
		anglebase = anglerange;
		anglerange = i;
	}
	anglerange -= anglebase;
	if (anglerange < 0) anglerange += 3600;

	/* determine the number of intervals to use for the arc */
	pieces = anglerange;
	while (pieces > 16) pieces /= 2;

	/* initialize the polygon */
	points = (pieces+1) * 2;
	if (poly->limit < points) (void)extendpolygon(poly, points);
	poly->count = points;
	poly->style = style;

	/* get the inner and outer radii of the arc */
	outerradius = radius + wid / 2;
	innerradius = outerradius - wid;

	/* fill the polygon */
	for(i=0; i<=pieces; i++)
	{
		a = (anglebase + i * anglerange / pieces) % 3600;
		sin = sine(a);   cos = cosine(a);
		poly->xv[i] = mult(cos, innerradius) + centerx;
		poly->yv[i] = mult(sin, innerradius) + centery;
		poly->xv[points-1-i] = mult(cos, outerradius) + centerx;
		poly->yv[points-1-i] = mult(sin, outerradius) + centery;
	}
	return(0);
}

/*
 * routine to make a polygon that describes the arcinst "ai" which is "len"
 * long and "wid" wide.  The polygon is in "poly", the style is set to "style".
 */
void makearcpoly(INTBIG len, INTBIG wid, ARCINST *ai, POLYGON *poly, INTSML style)
{
	REGISTER INTBIG x1, y1, x2, y2, e1, e2, angle;

	x1 = ai->end[0].xpos;   y1 = ai->end[0].ypos;
	x2 = ai->end[1].xpos;   y2 = ai->end[1].ypos;
	poly->style = style;

	/* zero-width polygons are simply lines */
	if (wid == 0)
	{
		if (poly->limit < 2) (void)extendpolygon(poly, 2);
		poly->count = 2;
		poly->xv[0] = x1;   poly->yv[0] = y1;
		poly->xv[1] = x2;   poly->yv[1] = y2;
		return;
	}

	/* determine the end extension on each end */
	e1 = e2 = wid/2;
	if ((ai->userbits&NOEXTEND) != 0)
	{
		/* nonextension arc: set extension to zero for all included ends */
		if ((ai->userbits&NOTEND0) == 0) e1 = 0;
		if ((ai->userbits&NOTEND1) == 0) e2 = 0;
	} else if ((ai->userbits&ASHORT) != 0)
	{
		/* shortened arc: compute variable extension */
		e1 = tech_getextendfactor(wid, ai->endshrink&0xFFFF);
		e2 = tech_getextendfactor(wid, (ai->endshrink>>16)&0xFFFF);
	}

	/* make the polygon */
	angle = (ai->userbits&AANGLE) >> AANGLESH;
	tech_makeendpointpoly(len, wid, angle*10, x1,y1, e1, x2,y2, e2, poly);
}

/*
 * routine to return the offset between the nominal width of arcinst "ai"
 * and the actual width.  This routine accesses the "arc_width_offset"
 * variable on the technology objects.
 */
INTBIG arcwidthoffset(ARCINST *ai)
{
	REGISTER ARCPROTO *ap;

	/* if the technology has its own routine, use it */
	ap = ai->proto;
	if (ap->tech->nodesizeoffset != 0)
		return((*(ap->tech->arcwidthoffset))(ai));

	return(arcprotowidthoffset(ap));
}

/*
 * routine to return the offset between the nominal width of arcproto "ap"
 * and the actual width.  This routine accesses the "arc_width_offset"
 * variable on the technology objects.
 */
INTBIG arcprotowidthoffset(ARCPROTO *ap)
{
	REGISTER INTBIG addr;

	/* make sure cache of information is valid */
	if (tech_arc_widoff == 0)
	{
		tech_initarcwidthoffset();
		if (tech_arc_widoff == 0) return(0);
	}

	addr = tech_arc_widoff[ap->tech->techindex];
	if (addr == 0) return(0);
	return(((INTBIG *)addr)[ap->arcindex]*ap->tech->deflambda/WHOLE);
}

/*
 * routine to return the name of the arc with function "fun"
 */
char *arcfunctionname(INTSML fun)
{
	static char *arcfunname[] =
	{
		"unknown",				/* APUNKNOWN */
		"metal-1",				/* APMETAL1 */
		"metal-2",				/* APMETAL2 */
		"metal-3",				/* APMETAL3 */
		"metal-4",				/* APMETAL4 */
		"metal-5",				/* APMETAL5 */
		"metal-6",				/* APMETAL6 */
		"metal-7",				/* APMETAL7 */
		"metal-8",				/* APMETAL8 */
		"polysilicon-1",		/* APPOLY1 */
		"polysilicon-2",		/* APPOLY2 */
		"polysilicon-3",		/* APPOLY3 */
		"diffusion",			/* APDIFF */
		"p-diffusion",			/* APDIFFP */
		"n-diffusion",			/* APDIFFN */
		"substrate-diffusion",	/* APDIFFS */
		"well-diffusion",		/* APDIFFW */
		"bus",					/* APBUS */
		"unrouted",				/* APUNROUTED */
		"nonelectrical"			/* APNONELEC */
	};

	if (fun < 0 || fun > APNONELEC) return("");
	return(arcfunname[fun]);
}

/******************** LAYER DESCRIPTION ********************/

/*
 * routine to return the full name of layer "layer" in technology "tech".
 * This routine accesses the "layer_names" variable on the technology objects.
 */
char *layername(TECHNOLOGY *tech, INTSML layer)
{
	REGISTER INTBIG addr;

	if (layer < 0) return("");

	/* make sure cache of information is valid */
	if (tech_layer_names == 0)
	{
		tech_initlayername();
		if (tech_layer_names == 0) return("");
	}

	addr = tech_layer_names[tech->techindex];
	if (addr == 0) return("");
	return(((char **)addr)[layer]);
}

/*
 * routine to return the function of layer "layer" in technology "tech".
 * This routine accesses the "layer_function" variable on the technology objects.
 */
INTBIG layerfunction(TECHNOLOGY *tech, INTSML layer)
{
	REGISTER INTBIG addr;

	if (layer < 0) return(LFUNKNOWN);

	/* make sure cache of information is valid */
	if (tech_layer_function == 0)
	{
		tech_initlayerfunction();
		if (tech_layer_function == 0) return(LFUNKNOWN);
	}

	addr = tech_layer_function[tech->techindex];
	if (addr == 0) return(LFUNKNOWN);
	return(((INTBIG *)addr)[layer]);
}

/* Routine to return nonzero if the layer function "fun" is metal  */
INTSML layerismetal(INTBIG fun)
{
	fun &= LFTYPE;
	if (fun == LFMETAL1 || fun == LFMETAL2 || fun == LFMETAL3 ||
		fun == LFMETAL4 || fun == LFMETAL5 || fun == LFMETAL6 ||
		fun == LFMETAL7 || fun == LFMETAL8)  return(1);
	return(0);
}

/* Routine to return nonzero if the layer function "fun" is polysilicon  */
INTSML layerispoly(INTBIG fun)
{
	fun &= LFTYPE;
	if (fun == LFPOLY1 || fun == LFPOLY2 || fun == LFPOLY3 || fun == LFGATE) return(1);
	return(0);
}

/* Routine to return nonzero if the layer function "fun" is a contact/via  */
INTSML layeriscontact(INTBIG fun)
{
	fun &= LFTYPE;
	if (fun == LFCONTACT1 || fun == LFCONTACT2 || fun == LFCONTACT3 ||
		fun == LFCONTACT4 || fun == LFCONTACT5 || fun == LFCONTACT6)  return(1);
	return(0);
}

/*
 * routine to return the height of layer function "funct".
 */
INTBIG layerfunctionheight(INTBIG funct)
{
	switch (funct & LFTYPE)
	{
		case LFWELL:       return(0);
		case LFSUBSTRATE:  return(1);
		case LFIMPLANT:    return(2);
		case LFTRANSISTOR: return(3);
		case LFRESISTOR:   return(4);
		case LFCAP:        return(5);
		case LFEMITTER:    return(6);
		case LFBASE:       return(7);
		case LFCOLLECTOR:  return(8);
		case LFGUARD:      return(9);
		case LFISOLATION:  return(10);
		case LFDIFF:       return(11);
		case LFPOLY1:      return(12);
		case LFPOLY2:      return(13);
		case LFPOLY3:      return(14);
		case LFGATE:       return(15);
		case LFCONTACT1:   return(16);
		case LFMETAL1:     return(17);
		case LFCONTACT2:   return(18);
		case LFMETAL2:     return(19);
		case LFCONTACT3:   return(20);
		case LFMETAL3:     return(21);
		case LFCONTACT4:   return(22);
		case LFMETAL4:     return(23);
		case LFCONTACT5:   return(24);
		case LFMETAL5:     return(25);
		case LFCONTACT6:   return(26);
		case LFMETAL6:     return(27);
		case LFMETAL7:     return(28);
		case LFMETAL8:     return(29);
		case LFPLUG:       return(30);
		case LFOVERGLASS:  return(31);
		case LFBUS:        return(32);
		case LFART:        return(33);
		case LFCONTROL:    return(34);
	}
	return(35);
}

INTSML get3dfactors(TECHNOLOGY *tech, INTBIG layer, INTBIG *height, INTBIG *thickness)
{
	static INTSML heightsetup = 0;
	REGISTER TECHNOLOGY *itech;
	REGISTER VARIABLE *varh, *vart;

	/* make sure that every technology has layer height information */
	if (heightsetup == 0)
	{
		heightsetup = 1;
		tech_techlayer3dheightkey = makekey("TECH_layer_3dheight");
		tech_techlayer3dthicknesskey = makekey("TECH_layer_3dthickness");
		for(itech = el_technologies; itech != NOTECHNOLOGY; itech = itech->nexttechnology)
		{
			varh = getvalkey((INTBIG)itech, VTECHNOLOGY, VINTEGER|VISARRAY, tech_techlayer3dheightkey);
			vart = getvalkey((INTBIG)itech, VTECHNOLOGY, VINTEGER|VISARRAY, tech_techlayer3dthicknesskey);
			if (varh == NOVARIABLE || vart == NOVARIABLE)
				tech_3ddefaultlayerheight(itech);
		}
	}

	if (tech != tech_3dcurtech)
	{
		tech_3dcurtech = tech;
		tech_3dcurheightvar = getvalkey((INTBIG)tech_3dcurtech, VTECHNOLOGY, VINTEGER|VISARRAY,
			tech_techlayer3dheightkey);
		tech_3dcurthicknessvar = getvalkey((INTBIG)tech_3dcurtech, VTECHNOLOGY, VINTEGER|VISARRAY,
			tech_techlayer3dthicknesskey);
		if (tech_3dcurheightvar == NOVARIABLE || tech_3dcurthicknessvar == NOVARIABLE)
		{
			tech_3dcurtech = NOTECHNOLOGY;
			return(1);
		}
	}
	if (layer < 0 || layer >= tech->layercount) return(1);

	*thickness = ((INTBIG *)tech_3dcurthicknessvar->addr)[layer];
	*height = ((INTBIG *)tech_3dcurheightvar->addr)[layer];
	return(0);
}

void set3dheight(TECHNOLOGY *tech, INTBIG *depth)
{
	setvalkey((INTBIG)tech, VTECHNOLOGY, tech_techlayer3dheightkey, (INTBIG)depth,
		VINTEGER|VISARRAY|(tech->layercount<<VLENGTHSH)|VDONTSAVE);
	tech_3dcurtech = NOTECHNOLOGY;
}

void set3dthickness(TECHNOLOGY *tech, INTBIG *thickness)
{
	setvalkey((INTBIG)tech, VTECHNOLOGY, tech_techlayer3dthicknesskey, (INTBIG)thickness,
		VINTEGER|VISARRAY|(tech->layercount<<VLENGTHSH)|VDONTSAVE);
	tech_3dcurtech = NOTECHNOLOGY;
}

/*
 * Routine to establish default layer height and thickness for
 * technology "tech".
 */
void tech_3ddefaultlayerheight(TECHNOLOGY *tech)
{
	REGISTER INTBIG *layerheight, *layerthickness, i, funct;

	layerheight = (INTBIG *)emalloc(tech->layercount * SIZEOFINTBIG, el_tempcluster);
	layerthickness = (INTBIG *)emalloc(tech->layercount * SIZEOFINTBIG, el_tempcluster);
	for(i=0; i<tech->layercount; i++)
	{
		funct = layerfunction(tech, (INTSML)i) & LFTYPE;
		layerheight[i] = layerfunctionheight(funct);
		layerthickness[i] = 0;
		if (layeriscontact(funct) != 0) layerthickness[i] = 2;
	}
	set3dheight(tech, layerheight);
	set3dthickness(tech, layerthickness);
	efree((char *)layerheight);
	efree((char *)layerthickness);
}

/*
 * routine to tell the minimum distance between layers "layer1" and "layer2" in
 * technology "tech".  If "connected" is zero, the two layers are not connected,
 * if it is nonzero, they are connected electrically.  A negative return means
 * that the two layers can overlap.  This routine accesses the database
 * variables "DRC_min_connected_distances" and "DRC_min_unconnected_distances"
 * in the technologies.
 */
INTBIG tech_getdrcmindistance(TECHNOLOGY *tech, INTSML layer1, INTSML layer2,
	INTSML connected)
{
	REGISTER INTSML pindex, temp;
	REGISTER INTBIG addr;

	if (layer1 < 0 || layer2 < 0) return(XX);

	/* make sure cache of information is valid */
	if (connected != 0)
	{
		if (tech_drcconndistance == 0)
		{
			tech_initmaxdrcsurround();
			if (tech_drcconndistance == 0) return(XX);
		}
		addr = tech_drcconndistance[tech->techindex];
	} else
	{
		if (tech_drcuncondistance == 0)
		{
			tech_initmaxdrcsurround();
			if (tech_drcuncondistance == 0) return(XX);
		}
		addr = tech_drcuncondistance[tech->techindex];
	}
	if (addr == 0) return(XX);

	/* compute index into connectedness tables */
	if (layer1 > layer2) { temp = layer1; layer1 = layer2;  layer2 = temp; }
	pindex = (layer1+1) * (layer1/2) + (layer1&1) * ((layer1+1)/2);
	pindex = layer2 + tech->layercount * layer1 - pindex;
	return(((INTBIG *)addr)[pindex]);
}

/*
 * routine to tell the maximum distance around layer "layer" that needs
 * to be looked at for design-rule checking.  This routine accesses the
 * database variable "DRC_max_distances" in the technologies.
 */
INTBIG maxdrcsurround(TECHNOLOGY *tech, INTSML layer)
{
	REGISTER INTBIG i, addr;

	if (layer < 0 || layer >= tech->layercount) return(XX);

	/* make sure cache of information is valid */
	if (tech_drcmaxdistances == 0)
	{
		tech_initmaxdrcsurround();
		if (tech_drcmaxdistances == 0) return(XX);
	}

	addr = tech_drcmaxdistances[tech->techindex];
	if (addr == 0) return(XX);
	i = ((INTBIG *)addr)[layer];
	if (i < 0) return(XX);
	i = i*tech->deflambda/WHOLE;
	return(i);
}

/*
 * routine to tell the minimum distance between two layers "layer1" and
 * "layer2".  If "connected" is zero, the two layers are not connected,
 * if it is nonzero, they are connected electrically.
 * A negative return means that the two layers can overlap.
 */
INTBIG drcmindistance(TECHNOLOGY *tech, INTSML layer1, INTSML layer2, INTSML connected)
{
	REGISTER INTBIG i;

	/* get the un-scaled distance */
	i = tech_getdrcmindistance(tech, layer1, layer2, connected);

	/* scale result for current lambda value */
	if (i > 0) i = i * tech->deflambda / WHOLE;
	return(i);
}

/*
 * routine to return the minimum width of layer "layer" in technology "tech".
 */
INTBIG drcminwidth(TECHNOLOGY *tech, INTSML layer)
{
	REGISTER INTBIG addr;

	/* make sure cache of information is valid */
	if (tech_drcminwidth == 0)
	{
		tech_initmaxdrcsurround();
		if (tech_drcminwidth == 0) return(0);
	}

	addr = tech_drcminwidth[tech->techindex];
	if (addr == 0) return(XX);
	return(((INTBIG *)addr)[layer]*tech->deflambda/WHOLE);
}

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

/*
 * The transformation that is done here is from one range specification
 * to another.  The original range is from "low" to "high".  The computed
 * area is from "newlow" to "newhigh".  The obvious way to do this is:
 *   center = (low + high) / 2;
 *   size = high - low;
 *   *newlow = center + size*lowmul/WHOLE + lowsum*lambda/WHOLE;
 *   *newhigh = center + size*highmul/WHOLE + highsum*lambda/WHOLE;
 * where "center" is the center co-ordinate and the complex expression is
 * the transformation factors.  However, this method is unstable for odd
 * range extents because there is no correct integral co-ordinate for the
 * center of the area.  Try it on a null transformation of (-1,0).  The
 * correct code is more complex, but rounds the center computation twice
 * in case it is not integral, adjusting the numerator to round properly.
 * The negative test is basically an adjustment by the "sign extend" value.
 *
 */
void subrange(INTBIG low, INTBIG high, INTBIG lowmul, INTBIG lowsum, INTBIG highmul,
	INTBIG highsum, INTBIG *newlow, INTBIG *newhigh, INTBIG lambda)
{
	REGISTER INTBIG total, size;

	size = high - low;
	if ((total = low + high) < 0) total--;

	/*
	 * Because the largest 32-bit number is 2147483647 and because WHOLE
	 * is 120, the value of "size" cannot be larger than 2147483647/120
	 * (which is 17895697) or there may be rounding problems.  For these large
	 * numbers, use "muldiv".
	 */
	if (size > 17895697)
	{
		*newlow = total/2 + muldiv(size, lowmul, WHOLE) + lowsum*lambda/WHOLE;
		*newhigh = (total+1)/2 + muldiv(size, highmul, WHOLE) + highsum*lambda/WHOLE;
	} else
	{
		*newlow = total/2 + size*lowmul/WHOLE + lowsum*lambda/WHOLE;
		*newhigh = (total+1)/2 + size*highmul/WHOLE + highsum*lambda/WHOLE;
	}
}

/*
 * routine to perform a range calculation (similar to "subrange") but on
 * only one value rather than two.  The extent of the range is from "low"
 * to "high" and the value of lambda is "lambda".  The routine returns
 * the value (low+high)/2 + (high-low)*mul/WHOLE + sum*lambda/WHOLE.
 */
INTBIG getrange(INTBIG low, INTBIG high, INTBIG mul,INTBIG sum, INTBIG lambda)
{
	REGISTER INTBIG total;

	total = low + high;
	if (total < 0) total--;

	/*
	 * Because the largest 32-bit number is 2147483647 and because WHOLE
	 * is 120, the value of "high-low" cannot be larger than 2147483647/120
	 * (which is 17895697) or there may be rounding problems.  For these large
	 * numbers, use "muldiv".
	 */
	if (high-low > 17895697)
		return(total/2 + muldiv(high-low, mul, WHOLE) + sum*lambda/WHOLE);
	return(total/2 + (high-low)*mul/WHOLE + sum*lambda/WHOLE);
}
