/*
 *			D M - M G . C
 *
 *  MGED display manager for Megatek 7250 color display system.
 *  This version only works over the parallel UNIBUS interface,
 *  with the BRL VAX UNIX Megatek driver.
 *
 *  Author -
 *	Michael John Muuss
 *  
 *  Source -
 *	SECAD/VLD Computing Consortium, Bldg 394
 *	The U. S. Army Ballistic Research Laboratory
 *	Aberdeen Proving Ground, Maryland  21005
 *  
 *  Copyright Notice -
 *	This software is Copyright (C) 1985 by the United States Army.
 *	All rights reserved.
 */
#ifndef lint
static char RCSid[] = "@(#)$Header: dm-mg.c,v 2.10 86/03/10 21:39:19 mike Exp $ (BRL)";
#endif

#include <fcntl.h>
#include <stdio.h>
#include "./machine.h"	/* special copy */
#include "../h/vmath.h"
#include "ged.h"
#include "dm.h"
#include "mgdev.h"
#include "solid.h"
#include "../h/mater.h"

typedef unsigned char u_char;
typedef long	displaylist_t;		/* VG displaylist */

extern void	perror(), sync();
extern char	*malloc();
extern long	lseek();

#define LARGE_TABLET	yes		/* true for BRL, not for UoUtah */

/* Display Manager package interface */

#define MGBOUND	15.75	/* Max magnification in Rot matrix */
int	Mg_open();
void	Mg_close();
int	Mg_input();
void	Mg_prolog(), Mg_epilog();
void	Mg_normal(), Mg_newrot();
void	Mg_update();
void	Mg_puts(), Mg_2d_line(), Mg_light();
int	Mg_object();
unsigned Mg_cvtvecs(), Mg_load();
void	Mg_statechange(), Mg_viewchange(), Mg_colorchange();
void	Mg_window(), Mg_debug();

struct dm dm_Mg = {
	Mg_open, Mg_close,
	Mg_input,
	Mg_prolog, Mg_epilog,
	Mg_normal, Mg_newrot,
	Mg_update,
	Mg_puts, Mg_2d_line,
	Mg_light,
	Mg_object,
	Mg_cvtvecs, Mg_load,
	Mg_statechange,
	Mg_viewchange,
	Mg_colorchange,
	Mg_window, Mg_debug,
	1,				/* has displaylist */
	MGBOUND,
	"mg", "Megatek 7250"
};

static void mg_start();

struct timeval	{			/* needed for select() */
	long	tv_sec;			/* seconds */
	long	tv_usec;		/* microseconds */
};
#define DEV_DISPLAYLIST "/dev/mgseek0"
#define DEV_SELREGS	"/dev/mgper0"

static displaylist_t	*zoombufp;	/* dyanmic buffer base of zoombuf */
static displaylist_t	*curzp;		/* "current" position in zoombuf */
static unsigned		zoombytes;	/* sizeof zoombuf */

#define MEM_END		(3*4*16384L)	/* Address of end of memory (bytes) */

static int	megfd;			/* raw DMA device file descr. */
static int	perfd;			/* Buffered register dev fd */

/* From /sys/h/ioctl.h */
#define	IOC_VOID	0x20000000	/* no parameters */
#define	_IO(x,y)	(IOC_VOID|('x'<<8)|y)
#define MGTPOLLON	_IO(M, 0)	/* Kernel polling of tablet */
#define MGTPOLLOFF	_IO(M, 1)	/* cease Kernel polling of tablet */

extern struct device_values dm_values;	/* values read from devices */

static int	mg_window[6];	/* X hi,lo;  Y hi,lo;  Z hi,lo */
static int	mg_debug;

/**** Global mode information ****/
extern int	adcflag;		/* A/D cursor on/off */
/**** End Global mode information ****/

/* Mapping from Megatek buttons to GED button functions.
 * map 0 is for viewing, and map 1 is for editing functions.
 */
static unsigned char bviewmap[16] = {
	BV_VRESTORE,	BV_VSAVE,	BE_S_ILLUMINATE,BV_SHIFT,
	BV_RESET,	BV_SLICEMODE,	BV_ADCURSOR,	BE_O_ILLUMINATE,
	BV_BOTTOM,	BV_LEFT,	BV_REAR,	BV_90_90,
	BV_TOP,		BV_RIGHT,	BV_FRONT,	BV_35_25
};
static unsigned char beditmap[16] = {
	BE_O_X,		BE_O_Y,		BE_ACCEPT,	BE_SHIFT,
	BE_O_SCALE,	BE_O_ROTATE,	BE_O_XY,	BE_REJECT,
	BE_S_SCALE,	BE_S_ROTATE,	BE_S_TRANS,	BE_S_EDIT,
	BE_O_XSCALE,	BE_O_YSCALE,	BE_O_ZSCALE,		0
};
static int bviewflag = 1;
#define SHIFT_BUTTON	3	/* button # to xch view & edit modes */

/* bit 0 == light 0 */
static unsigned short lights[2];/*  [0] for view, [1] for edit */

/* Maps BE_ and BV_ functions into {mapno, button} pairs */
static struct fun_map  {
	unsigned char fm_map;	/* Which button map function is in */
	unsigned char fm_button;/* Which button function is on */
} invmap[BV_MAXFUNC+1];
static void fscd();
static void mg_loadcmap();

/* Value for MDL_CONTROL:  Graphics Engine control register */
static displaylist_t engine_start = MEG_WORD( 0x11, 0 ); /* RUN */
static displaylist_t engine_stop  = MEG_WORD( 0x10, 0 ); /* --- */

/* Value for MDL_LIST_START:  STARTING address of primary display list */
static displaylist_t list_init = MEG_WORD( 0, MDL_ASEGTAB );

/*
 * Color Map table
 */
static struct rgbtab {
	unsigned char	r;
	unsigned char	g;
	unsigned char	b;
} mg_rgbtab[16] = {
	{0,0,0},	/* 0 MUST BE Black */
	{15,0,0},	/* 1 red */
	{0,0,15},	/* 2 blue */
	{15,15,0},	/* 3 yellow */
#define FIRSTFREESLOT	4	/* all entries here to 14 are user-filled */
	{0,15,0},	/* 4 green */
	{15,0,15},	/* 5 magenta */
	{0,15,15},	/* 6 cyan */
	{11,9,3},	/* 7 orange */
	{3,11,9},	/* 8 ??? */
	{9,3,11},	/* 9 purple */
	{15,7,0},	/* 10 Doug's orange */
	{7,15,0},	/* 11 Doug's ??? */
	{7,0,15},	/* 12 Doug's purple */
	{15,7,7},	/* 13 pink */
	{7,7,7},	/* 14 gray */
	{15,15,15}	/* 15 MUST BE white */
};
#define NSLOTS		(15-FIRSTFREESLOT)	/* Saves 0..3, 15 */

#define MGC_BLACK	0
#define MGC_RED		1			/* same as DM_RED */
#define MGC_BLUE	2
#define MGC_YELLOW	3
#define MGC_WHITE	15			/* NOT same as DM_WHITE */

static short fscd_reset[] = { MGP_FSCD, 0x0001 };
static short fscd_swlt[] = { MGP_FSCD, 0x4AAC };/* uncouple, 0, int on press, off */
static short fscd_offlight[] = { MGP_FSCD, 0x4004 };	/* light off */
static short fscd_onlight[] = { MGP_FSCD, 0x4005 };	/* light on */
static short fscd_knob[] = { MGP_FSCD, 0x8FC8 };
static short fscd_adg[] = { MGP_FSCD, 0x0003 };

static long dl_fscd[] = {
	MEG_WORD( -2048, 2047 ),	/* Minimum value, Maximum value */
	MEG_WORD( 2048, 0 ),	/* Gain (0 to 65,535), Current value */
	MEG_WORD( 0x1, 0x1 ),	/* Threshold, Frequency */
	MEG_WORD( 0, 0 ),	/* Valuator 0, Valuator 1 */
	MEG_WORD( 0, 0 ),	/* Valuator 2, Valuator 3 */
	MEG_WORD( 0, 0 ),	/* Valuator 4, Valuator 5 */
	MEG_WORD( 0, 0 ),	/* Valuator 6, Valuator 7 */
	MEG_WORD( 0, 0xFFFF ),	/* Unused, Switch/Light Select (Sw 0-->LSB) */
	MEG_WORD( 0xFFFF, 0xFFFF ),	/* Switch/Light info P V LT (4 bits) */
	MEG_WORD( 0xFFFF, 0xFFFF ),	/* Switch/Light info (Sw 0 -->LSB) */
	MEG_WORD( 0, 0 ),	/* Sw. position, Sw. value (Sw 0 -->LSB) */
	MEG_WORD( 0, 0 )	/* Light value when last read (1-->light ON) */
};


static short dt_enable[] = { MGP_IPCU, 0x0C00 };
static long dl_digit[] = {
	0xF8F80001,		/* XOFF, YOFF, Tracking interupt control */
	0,			/* X, Y */
	0,			/* Button info */
	0XF800F800,		/* Minimum X, Y */
	0x07FF07FF,		/* Maximum X, Y */
	MGI_A_DRAW(0,0,0),	/* Abs Black Draw, with X, Y from tablet */
	MGI_RET,		/* MGI_RET */
	0			/* Pick module control */
};

static long dl_zeros[16] = { 0 };	/* To clear out low memory */

/*
 *			M G _ O P E N
 *
 * Fire up the display manager, and the display processor.
 *
 */
Mg_open()
{
	register int i, j;
	register int count;
	char line[64], line2[64], line3[64];

	/* Protect ourselves. */
	sync();

	/* Find out which Megatek we are using */
	(void)printf("Which Megatek [0]? ");
	(void)gets( line );		/* Null terminated */
	if( feof(stdin) )
		quit();
	if( line[0] != '\0' )  {
		(void)sprintf( line2, "/dev/mgseek%s", line );
		(void)sprintf( line3, "/dev/mgper%s", line );
		if( (megfd = open(line2, O_RDWR)) < 0 )  {
			perror(line2);
			return(1);		/* BAD */
		}
		/* This device can perform SELECTs */
		if( (perfd = open( line3, O_RDWR)) < 0 )  {
			perror( line3 );
			return(1);		/* BAD */
		}
	} else {
		/* Default is 0, get device open */
		if( (megfd = open(DEV_DISPLAYLIST, O_RDWR)) < 0 )  {
			perror(DEV_DISPLAYLIST);
			return(1);		/* BAD */
		}
		/* This device can perform SELECTs */
		if( (perfd = open( DEV_SELREGS, O_RDWR)) < 0 )  {
			perror( DEV_SELREGS );
			return(1);		/* BAD */
		}
	}

#define NSOLIDS	1000
	/* This is clearly wrong.  This probably ought to be
	 *  done more dynamicly....  Also, the error checking
	 *  should probably be improved!
	 */
	zoombytes = (NSOLIDS * 9 * sizeof(displaylist_t)) + 1200;
	zoombufp = (displaylist_t *) malloc( zoombytes+1024 );
	if( zoombufp == (displaylist_t *)NULL )  {
		(void)printf("Mg_open: unable to malloc(%d) for zoombuf\n",
			zoombytes);
		return(1);		/* BAD */
	}

	/* For all possible button presses in both button maps,
	 * build a table to function to button/light mappings.
	 */
	for( i=0; i < 16; i++ ) {
		if( (j = bviewmap[i]) != 0 )  {
			invmap[j].fm_map = 0;/* view map */
			invmap[j].fm_button = i;
		}
	}
	for( i=0; i < 16; i++ )  {
		if( (j = beditmap[i]) != 0 )  {
			invmap[j].fm_map = 1;/* edit map */
			invmap[j].fm_button = i;
		}
	}

	/*
	 * Free all memory which is not:
	 *	Reserved Megatek Low core (through ASEGTAB), and not
	 *	part of the control list (zoombuf).
	 */
	count = (MDL_ASEGTAB<<2) + zoombytes;
	memfree( &(dmp->dmr_map), (unsigned)(MEM_END - count), (unsigned long)count );

	/* Start display processor */
	mg_start();
	return(0);
}

static void
mg_write( fd, addr, count )
int fd;
char *addr;
unsigned count;
{
	register int i;

	i = write( fd, addr, count );
	if( i != count )  {
		perror("Megatek");
		(void)printf("mg_write(%d., x%x, %d.) = %d.\n",
			fd, (int)addr, count, i );
	}
}

/*
 * 			I N I T _ K N O B S
 * 
 * As initializing the knobs is a complex thing, and needed
 * in several places, we make it a function.
 */
static void
init_knobs()
{
	register int i;

	for( i = 0; i < 8; i++ ) {
		/* Initialize parms for this knob.
		 * Seems important to re-write the values each time.
		 * We send just the first 3 words.
		 */
	   	(void)lseek( megfd, 0xC0<<2, 0 );
		mg_write( megfd, (char *)dl_fscd, 3 * 4 );

		fscd_knob[1] = 0x8FC8 | i;

		mg_write( perfd, (char *)fscd_knob, sizeof(fscd_knob) );
	}
}

static void
mg_loadcmap()
{
	static displaylist_t color_map[16];
	register displaylist_t *cm;
	register struct rgbtab *rgb;
	register int i;

	/* Convert from internal format to Megatek format */
	cm = color_map;
	*cm++ = MEG_WORD( 0, 0 );	/* Black.  R,G,B,Inten=0 */
	rgb = &mg_rgbtab[1];
#define COLOR(r,g,b,i)	((r<<12)|(g<<8)|(b<<4)|i)
	for( i=1; i < 16; i++, rgb++ )
		*cm++ = MEG_WORD( 0, COLOR(rgb->r, rgb->g, rgb->b, 12) );

	/* DMA color map */
	(void)lseek( megfd, MDL_COLORMAP<<2, 0 );
	mg_write( megfd, (char *)color_map, sizeof color_map );
}

/*
 *  			M G _ S T A R T
 *  
 *  Go throught the lengthy procedure of actually starting
 *  the display processor.  This is a separate routine because
 *  it is needed to restart the display processor from Mg_restart,
 *  as well as start it the first time from Mg_init.
 */
static void
mg_start()
{
	register int i;
	char kaa[6];

	meg_engine(0);		/* Halt! */
	/* Clear low display list */
	(void)lseek( megfd, 0L, 0 );
	for( i=0; i < 32; i++ )
		mg_write( megfd, (char *)dl_zeros, sizeof(dl_zeros) );
	meg_engine(1);		/* Gentlemen, start your engines */

	/* Control block for the FSCD */
	(void)lseek( megfd, 0xC0<<2, 0 );
	mg_write( megfd, (char *)dl_fscd, sizeof(dl_fscd) );

	/* Control block for the Data Tablet ("digitizer") */
	(void)lseek( megfd, 0x48<<2, 0 );
	mg_write( megfd, (char *)dl_digit, sizeof(dl_digit) );

	/* Initialise the Function Switches & Control Dials (FSCD) */
	mg_write( perfd, (char *)fscd_reset, sizeof(fscd_reset) );
	mg_write( perfd, (char *)fscd_swlt, sizeof(fscd_swlt) );
	init_knobs();

	mg_write( perfd, (char *)fscd_adg, sizeof(fscd_adg) );

	/* Enable the data tablet */
	mg_write( perfd, (char *)dt_enable, sizeof(dt_enable) );

	/* Done once here, for initialization purposes */
	Mg_prolog();
	Mg_normal();
	Mg_puts( "GED Display Monitor Initialized", -1500, 200, 3, DM_BLUE );
	for( i=0; i<16; i++ )  {
		kaa[0] = 'A'+i;
		kaa[1] = 0;
		Mg_puts( kaa, -1500+(150*i), -800, 3, i );
	}
	Mg_epilog();
	Mg_update();		/* send displaylist to processor */

	/* Initialize color map */
	mg_loadcmap();

	/* Set display list start address */
	(void)lseek( megfd, MDL_LIST_START<<2, 0 );
	mg_write( megfd, (char *)&list_init, sizeof(list_init) );
}

/*
 *  			M G _ C L O S E
 *  
 *  Gracefully release the display.
 */
void
Mg_close()
{
	register int i;

	Mg_prolog();
	Mg_epilog();
	Mg_update();		/* Write, then wait 1 frame time ...*/

	/* can't really stop the display yet, so write a no-picture frame */
	curzp = zoombufp;
	Mg_epilog();
	Mg_update();

	meg_engine(0);		/* Halt! */

	/* Clear low display list */
	(void)lseek( megfd, 0L, 0 );
	for( i=0; i < 32; i++ )
		mg_write( megfd, (char *)dl_zeros, sizeof(dl_zeros) );

	(void)close( perfd );
	(void)close( megfd );
	megfd = perfd = -1;
	free( (char *)zoombufp );
}

#define MGI_PR(x,y)		MEG_WORD( 0xC000|(x&0xFFF), 0x9000|(y&0xFFF) )
#define MGI_CURSON(d)		MEG_WORD( 0xE00D, 0x1000|(d<<13) )
#define MGI_CURSOFF(d)		MEG_WORD( 0xE00D, 0x0000|(d<<13) )
#define MGI_R_MOVE(x,y)		MEG_WORD( 0xC000|(x&0xFFF), 0x5000|(y&0xFFF) )
#define MGI_R_DRAW(x,y,c)	MEG_WORD( 0x4000|(x&0xFFF), (c<<12)|(y&0xFFF))

/*
 *			M G _ P R O L O G
 *
 * Write the **constant length** control portion of the displaylist
 * (the prolog) on the front of zoombuf.
 * Sets static (local) pointer to first free control-list location.
 * There are global variables which are parameters to this routine.
 */
void
Mg_prolog()
{
	register displaylist_t *zp;

	zp = zoombufp;		/* start at the beginning */
	*zp++ = MGI_ENABLE;		/* Enable display #1 */

	/* Indicate to the rest of the guys where dynamic part starts */
	curzp = zp;
}

/*
 *			M G _ E P I L O G
 */
void
Mg_epilog()
{
	/*
	 * A Point, in the Center of the Screen.
	 * This is drawn last, to always come out on top.
	 */
	*curzp++ = MGI_A_MOVE( 0, 0 );
	*curzp++ = MGI_Z_MOVE( 0 );
	*curzp++ = MGI_PA( 0, 0 );

	/* Draw the hardware cursor */
	*curzp++ = MGI_JMS( 0115 );	/* Digitizer GOTO block */
	/* Does an absolute vector in black to tablet position */
	*curzp++ = MGI_JMS( 0xC330 );	/* cursor, first display */
	/* ROM cursor does not return, but, for safety... */
	*curzp++ = MGI_END;	/* End of display list */

	/* Check for overflow.  It's too late, but... */
	if( ((int)curzp) > (((int)zoombufp)+zoombytes) )
		(void)printf("Mg_epilog: curzp=x%x, end=x%x !!\n", curzp,
			((int)zoombufp)+zoombytes );
}

/*
 *  			M G _ N E W R O T
 *  Stub.
 */
/* ARGSUSED */
void
Mg_newrot(mat)
mat_t mat;
{
	return;
}

/*
 *  			M G _ O B J E C T
 *  
 *  Set up for an object, transformed as indicated, and with an
 *  object center as specified.  The ratio of object to screen size
 *  is passed in as a convienience.
 *
 *  Returns 0 if object could be drawn, !0 if object was omitted.
 */
int
Mg_object( sp, mat, ratio, white )
register struct solid *sp;
mat_t mat;
double ratio;
{
	static mat_t mat_xform;		/* intermediate Megatek matrix */
	static mat_t mat_screen;	/* xform from prototype to screen */
	static mat_t mat_trnrot;	/* transposed Rot matrix */
	static vect_t pos_screen;	/* Pos, in (rotated) view space */
	extern int ndrawn;		/* from dozoom (quick hack) */
	register int color;

	/*
	 * Compute transfomation from model to view co-ordinates,
	 * to correctly position prototype on the screen.
	 * Take into account prototypes actual size (-1..+1) by
	 * multiplying by ratio.
	 */
	/* Compute view-space pos of solid's center */
	MAT4X3PNT( pos_screen, mat, sp->s_center );

	/* Save rotation portion of mat, add xlate */
	mat_copy( mat_trnrot, mat );
	mat_trnrot[15] = 1;
	mat_trnrot[MDX] = pos_screen[X];
	mat_trnrot[MDY] = pos_screen[Y];
	mat_trnrot[MDZ] = pos_screen[Z];

	/* Also include scaling, given prototype size -vs- view */
	mat_idn(mat_xform);
	mat_xform[0] = ratio;	/* * s_size / ( 2 * m2view[15] ) */
	mat_xform[5] = ratio;
	mat_xform[10] = ratio;

	/* Glue it together.  Important to scale BEFORE rot+xlate, so
	 * as not to damage the length of the (screen space) xlate */
	mat_mul( mat_screen, mat_trnrot, mat_xform );
	if(
		mat_screen[ 0] < -MGBOUND ||
		mat_screen[ 0] >  MGBOUND ||
		mat_screen[ 5] < -MGBOUND ||
		mat_screen[ 5] >  MGBOUND ||
	 	mat_screen[MDX] < -MGBOUND ||
		mat_screen[MDX] >  MGBOUND ||
		mat_screen[MDY] < -MGBOUND ||
		mat_screen[MDY] >  MGBOUND
	)  return(0);	/* BAD */

	/* Phony pseudo-color, for now */
#ifdef never
	if( meg_segh( 0, 0, 0, mat_screen, white ?
		MGC_WHITE :
		((ndrawn>>2)%13)+2, 42 )
	)  {
		return(0);	/* BAD */
	}
#else
	if( white || sp->s_materp == (char *)0 )
		color = MGC_WHITE;
	else
		color = ((struct mater *)sp->s_materp)->mt_dm_int;
	if( meg_segh( 0, 0, 0, mat_screen, color, 42 ) )  {
		return(0);	/* BAD */
	}
#endif

	*curzp++ = MGI_JMS( sp->s_addr>>2 );
	return(1);	/* OK */
}

/*
 *			M G _ M A T C A L L
 *
 * Generate a call to a segment (generated by GED2).
 * On the megatek, we implement this by writing a segment header
 * including a new transformation matrix,
 * and then doing a subroutine call.
 */
void
mg_matcall( disp_addr, mat_screen, color )
int	disp_addr;		/* address in display processor */
mat_t	mat_screen;		/* screen position of center of segment */
{
	if( ((int)curzp) > (((int)zoombufp)+zoombytes) )  {
		(void)printf("Mg_matcall: curzp=x%x, end=x%x !!\n", curzp,
			((int)zoombufp)+zoombytes );
		return;
	}

	(void)meg_segh( 0, 0, 0, mat_screen, color, 42 );

	*curzp++ = MGI_JMS( disp_addr>>2 );
}

/*
 *			M G _ N O R M A L
 *
 * Restore the display processor to a normal mode of operation
 * (ie, not scaled, rotated, displaced, etc).
 * Turns off windowing.
 */
void
Mg_normal()
{
	*curzp++ = MGI_NO_XLATE;
}

/*
 *			M G _ U P D A T E
 *
 * Transmit accumulated displaylist to the display processor.
 */
void
Mg_update()
{
	/* Write out control portion of display list */
	(void)lseek( megfd, MDL_ASEGTAB<<2, 0 );
	if( (unsigned)write( megfd, (char *)zoombufp, zoombytes )
							!= zoombytes )  {
		perror("Mg_update");
		release();		/* detach this display */
	}
}

/*
 *			M G _ P U T S
 *
 * Output a string into the displaylist.
 * The starting position of the beam is as specified.
 */
void
Mg_puts( str, x, y, size, color )
register u_char *str;
{
	register u_char *ccp;

	if( ((int)curzp) > (((int)zoombufp)+zoombytes) )  {
		(void)fprintf(stderr,"Mg_puts: curzp=x%x, end=x%x !!\n", curzp,
			((int)zoombufp)+zoombytes );
		return;
	}

	/* Numbers extend to the right -- make sure they don't overflow */
#define LIM	2020	/* don't let numbers get to the edge */
	if( x < -2048 || x > LIM || y < -2048 || y > LIM )
		return;		/* offscreen */
#undef LIM

	switch( size ) {
	case 0:
	default:
		size = MGCH_S2;
		break;
	case 1:
		size = MGCH_S3;
		break;
	case 2:
		size = MGCH_S5;
		break;
	case 3:
		size = MGCH_S6;
		break;
	}

	/* This handles out-of-range, plus DM_WHITE != MGC_WHITE */
	if( color < MGC_BLACK || color > MGC_YELLOW )  color = MGC_WHITE;

	*curzp++ = MGI_A_MOVE( x, y );
	*curzp++ = MEG_WORD( MGCH(size,color), (str[0]<<8)|str[1] );
	if( *str++ == 0 || *str++ == 0 )
		return;
	ccp = (u_char *) curzp;

	/*
	 * Megatek likes null terminated strings.  How nice.
	 * Pitty it wants them in "Big-Endian" byte order.
	 * What an amazing kludge:  exclusive-OR'ing the pointer
	 * with 03 correctly converts the pointer to Big-Endian.
	 */
	while( *( (u_char *)(((int)(ccp++))^03) ) = *str++ )
		;

	while( ((int)ccp) & 03 )
		*( (u_char *)(((int)(ccp++))^03) ) = '\0';

	curzp = (displaylist_t *) ccp;
}

/*
 *			M G _ 2 D _ G O T O
 *
 */
void
Mg_2d_line( x1, y1, x2, y2, dashed )
int x1, y1;
int x2, y2;
int dashed;
{
	register displaylist_t *zp = curzp;


	*zp++ = MGI_A_MOVE( x1, y1 );
	if( dashed )
		*zp++ = MGI_A_DASH( x2, y2, MGC_YELLOW );
	else
		*zp++ = MGI_A_DRAW( x2, y2, MGC_YELLOW );
	curzp = zp;
}

/*
 *			M E G _ S E G H
 */
static
meg_segh( dx, dy, dz, mat, color, pick_label )
int dx, dy, dz;
register mat_t mat;
int color;
int pick_label;
{
	register displaylist_t *zp = curzp;

	/* Check for overflow.  It's too late, but... */
	if( ((int)curzp) > (((int)zoombufp)+zoombytes-1000) )  {
		return(1);	/* not done */
	}

	/*
	 * Segment Transformed Header
	 */
	if( pick_label )
		*zp++ = MEG_WORD( 0xF9C6, pick_label );
	else
		*zp++ = MEG_WORD( 0xF986, 0x0000 );/* Not pickable, label=0 */

	/*
	 * x origin=0, HCRST on, y origin=0
	 * dashed line on!!! (IMPORTANT, see below)
	 */
	*zp++ = MEG_WORD( 0x2000|dx, 0x7000|dy );	/* both heads */

	/*
	 * What an incredible kludge...Since there isn't a
	 * draw_dashline_with_previously_set_color, we must
	 * use a dashed line with all bits on to simulate
	 * a solid line.  The Vector Dash Control instruction
	 * is used when a dashed pattern is needed.
	 * Solid dash pattern, color
	 */
	*zp++ = MEG_WORD( 0xFFFF, color<<12 );

	/* Lower X,Y window (clipping) boundary  [1], [3] */
	*zp++ = MEG_WORD( (0xD<<12)|(mg_window[1]&0xFFF),
		(0x4<<12)|(mg_window[3]&0xFFF) );

	/* Upper X,Y window (clipping) boundary  [0], [2] */
	*zp++ = MEG_WORD( (0xD<<12)|(mg_window[0]&0xFFF),
		(0xC<<12)|(mg_window[2]&0xFFF) );

	/* The 8 elements of the transform matrix (not 12, or 16, alas) */
	/* NOTE:  The matrix we accept is in standard format, but
	 *  the Megatek thinks about it like Neuman/Sproul.
	 */
#define FTOF(x)	((int)((x)*2048.0))
	*zp++ = MEG_WORD( FTOF(mat[ 0]), FTOF(mat[ 1]) );
	*zp++ = MEG_WORD( FTOF(mat[ 3]), FTOF(mat[ 4]) );
	*zp++ = MEG_WORD( FTOF(mat[ 5]), FTOF(mat[ 7]) );
	*zp++ = MEG_WORD( FTOF(mat[ 2]), FTOF(mat[ 6]) );

	/* Z orig=0 & end of segment transform header */
	*zp++ = MEG_WORD( 0xFE80, (1<<12)|(dz) );
	curzp = zp;
	return(0);		/* drawn OK */
}
/*
 *			M E G _ E N G I N E
 */
static
meg_engine(on)
{
	/* Do magic poke to start or stop Graphics Engine */
	(void)lseek( megfd, MDL_CONTROL<<2, 0 );
	if( on )
		mg_write( megfd, (char *)&engine_start, sizeof(engine_start) );
	else
		mg_write( megfd, (char *)&engine_stop, sizeof(engine_stop) );
}

#define REG_WORDS	0xD0	/* # of 32-bit words of control block */
static long regs[REG_WORDS];
static long oldregs[REG_WORDS];
static short *knobp = (short *)&regs[0xC3];	/* valuator area */
#define KNOB(n)		knobp[((n)^1)]		/* knob value, odd layout */

static struct  op {
	unsigned short	cmd;
	unsigned short	val;
} opbuf[64];

/*
 *			M G _ I N P U T
 *
 * Execution must suspend in this routine until a significant event
 * has occured on either the command stream, or a device event has
 * occured, unless "noblock" is set.
 *
 * The GED "generic input" structure is filled in.
 *
 * Returns:
 *	0 if no command waiting to be read,
 *	1 if command is waiting to be read.
 */
Mg_input( cmd_fd, noblock )
{
	static long readfds;
	static struct timeval timeout;
	register int i;

	/*
	 * Check for input on the keyboard or on the polled registers.
	 *
	 * Suspend execution until either
	 *  1)  User types a full line
	 *  2)  A change in peripheral status occurs
	 *  3)  The timelimit on SELECT has expired
	 *
	 * If a RATE operation is in progress (zoom, rotate, slew)
	 * in which the peripherals (rate setting) may not be changed,
	 * but we still have to update the display,
	 * do not suspend execution.
	 */
	if( noblock )
		timeout.tv_sec = 0;
	else
		timeout.tv_sec = 600;
	timeout.tv_usec = 0;

	readfds = (1<<cmd_fd) | (1<<perfd);	/* STDIN + REGs */
	(void)select( 32, &readfds, 0L, 0L, &timeout );

	/*
	 * Read ALL the control blocks in the Megatek displaylist;
	 * it's faster than trying to pick out a word here and there.
	 * Our inputs will consist of values in control blocks
	 * (eg, for the data tablet), and interupt messages from
	 * the peripheral bus (eg, button presses).
	 */
	for( i=0; i<REG_WORDS; i++ )
		oldregs[i] = regs[i];

	(void)lseek( megfd, 0L, 0 );
	(void)read( megfd, (char *)regs, sizeof regs );

	dm_values.dv_buttonpress = 0;

	/*
	 * Trim and sign extend data tablet values
	 * The desired range is -2048 <= x,y <= +2048.
	 */
	i = (regs[0x4d]>>16) & 0x0FFF;
	if( i & 0x0800 )
		i |= (-1) & ~0x0FFF;	/* Sign extend */
#ifndef LARGE_TABLET
	i <<= 1;
#endif
	dm_values.dv_xpen = i;

	i = (regs[0x4d] & 0x0FFF);
	if( i & 0x0800 )
		i |= (-1) & ~0x0FFF;	/* Sign extend */
#ifndef LARGE_TABLET
	i <<= 1;
#endif
	dm_values.dv_ypen = i;

	dm_values.dv_penpress = 0;

	if( mg_debug > 1 )  {
		(void)printf("Using FSCD values:\n");
		for( i=0xC0; i<0xCC; i++ )
			(void)printf("x%x=x%x\n", i, regs[i] );
	}

	/* Zoom -- Knob #7. -1.0 <= zoom <= +1.0 */
	dm_values.dv_zoom = mglimit(KNOB(7)) / 2048.0;

	/* Slew */
	dm_values.dv_xslew = mglimit(KNOB(1)) / 2048.0;
	dm_values.dv_yslew = mglimit(KNOB(3)) / 2048.0;

	/* Due to a shortage of knobs, we multiplex the
	 * pseudo-joystick and the A/D cursor onto the same knobs.
	 */
	if( adcflag )  {
		/* Angle/Distance cursor */
		dm_values.dv_xadc = KNOB(0);
		dm_values.dv_yadc = KNOB(2);
		dm_values.dv_1adc = KNOB(4);
		dm_values.dv_2adc = KNOB(6);
		dm_values.dv_distadc = KNOB(5);
		dm_values.dv_flagadc = 0;
	}  else  {
		dm_values.dv_xjoy = mglimit(KNOB(0)) / 2048.0;
		dm_values.dv_yjoy = mglimit(KNOB(2)) / 2048.0;
		dm_values.dv_zjoy = mglimit(KNOB(4)) / 2048.0;
	}

	/*
	 * Build device interface structure for GED.
	 * First, process any interupt messages that came in.
	 */
	if( readfds & (1<<perfd) )  {
		register struct op *op = opbuf;

		i = read( perfd, (char *)opbuf, sizeof(opbuf) );
		if( i <= 0 )
			(void)printf("per read of %d.  readfds=x%x\n", i, readfds );
		while( i > 0 )  {
			if( mg_debug > 1 )
				(void)printf("MGper: %x val %x", op->cmd, op->val );

			switch( op->cmd & 0x3F )  {
			case MGP_IPCU:
				switch( (op->val>>12) & 0xF )  {
				case 0x0:
					/* From-kernel "tablet change" note */
					break;
				case 0x2:
					/* Tablet */
					if( mg_debug )
						(void)printf(" pen press");
					/* 4-button puck gives:
					 *  0x80 -- yellow
					 *  0x40 -- white
					 *  0x20 -- blue
					 *  0x10 -- green
					 */
					switch( (op->val>>4) & 0xF )  {
					case 8:		/* yellow */
						dm_values.dv_penpress = DV_PICK;
						break;
					case 4:		/* white */
						dm_values.dv_penpress = DV_OUTZOOM;
						break;
					case 2:		/* blue */
						dm_values.dv_penpress = DV_SLEW;
						break;
					case 1:		/* green */
						dm_values.dv_penpress = DV_INZOOM;
						break;
					}
					break;
				case 0x4:
					/* Joystick, such as it is */
					(void)printf("Megatek joystick?\n");
					break;
				case 0x8:
					/* Something like a keyboard */
					(void)printf("Megatek keyboard input?\n");
					break;
				}
				break;
			case MGP_FSCD:
				fscd( (int)((op->val>>12) & 0xF),
					(int)(op->val & 0xF)
				);
				break;
			case MGP_PICK:
				/* Pick module;  not used, but it speaks */
				break;
			default:
				(void)printf("MGper (unexpected): %x val %x\n",
					op->cmd, op->val );
				break;
			}
			i -= 4;
			op++;
			if( mg_debug )  (void)printf("\n");
		}
	}

#ifdef never
	dm_values.dv_xjoy = vglimit( regs.vg_jx>>4 ) / 2047.0;
	dm_values.dv_yjoy = vglimit( regs.vg_jy>>4 ) / 2047.0;
	dm_values.dv_zjoy = vglimit( regs.vg_jz>>4 ) / 2047.0;
#endif never

	if( readfds & (1<<cmd_fd) )
		return(1);		/* command awaits */
	else
		return(0);		/* just peripheral stuff */
}

/* 
 *			F S C D
 *
 * A local procedure to deal with messages from the function switch
 * and control dials unit.
 */
static void
fscd( from, number )
register int number;
{
	switch( from )  {

	case 0x1:
		/* "Valuator" (knob) */
		if( mg_debug )
			(void)printf("knob %d=%d", number, KNOB(number) );

		/* If ADC knob, set dv_flagadc */
		if( number==0 || number==2 || number == 4 || 
		    number==6 || number==5 )
			dm_values.dv_flagadc = 1;
		break;

	case 0x2:
		/* Button */
		if( mg_debug )
			(void)printf("%s button %d",
				bviewflag ? "View" : "Edit", number );

		if( number == SHIFT_BUTTON )  {
/******			init_knobs();	/* back to zeros */
			if( bviewflag )  {
				bviewflag = 0;
				Mg_light( LIGHT_ON, BE_SHIFT );
			}  else  {
				bviewflag = 1;
				Mg_light( LIGHT_OFF, BV_SHIFT );
			}
			break;
		}
		if( bviewflag )
			dm_values.dv_buttonpress = bviewmap[number];
		else
			dm_values.dv_buttonpress = beditmap[number];
		break;
	}
}

/* 
 *			M G _ L I G H T
 *
 * This function must keep both the light hardware, and the software
 * copy of the lights up to date.  Note that requests for light changes
 * may not actually cause the lights< to be changed, depending on
 * whether the buttons are being used for "view" or "edit" functions
 * (although this is not done in the present code).
 */
void
Mg_light( cmd, func )
int cmd;
int func;			/* BE_ or BV_ function */
{
	register int i;
	long ltemp;		/* Light temporary */

	switch( cmd )  {
	case LIGHT_RESET:
		lights[0] = lights[1] = 0;
		break;
	case LIGHT_ON:
		i = invmap[func].fm_map;
		lights[i] |= 1 << invmap[func].fm_button;
		break;
	case LIGHT_OFF:
		i = invmap[func].fm_map;
		lights[i] &= ~(1 << invmap[func].fm_button);
		break;
	}
	/* Update the lights box.  First extinguish OFF lights, and then
	 * light up ON lights.  Take into account which mode the buttons
	 * are in.
	 */
	if( bviewflag )
		ltemp = ~lights[0];
	else
		ltemp = ~lights[1];
	(void)lseek( megfd, (MDL_FSCD+7L)<<2, 0 );
	mg_write( megfd, (char *)&ltemp, sizeof ltemp );
	mg_write( perfd, (char *)fscd_offlight, sizeof fscd_offlight );


	if( bviewflag )
		ltemp = lights[0];
	else
		ltemp = lights[1];
	(void)lseek( megfd, (MDL_FSCD+7L)<<2, 0 );
	mg_write( megfd, (char *)&ltemp, sizeof ltemp );
	mg_write( perfd, (char *)fscd_onlight, sizeof fscd_onlight );
}

/*
 *			M G L I M I T
 *
 * Because the FSCD dials are so sensitive, setting them exactly to
 * zero is very difficult.  This function can be used to extend the
 * location of "zero" into "the dead zone".
 */
static
mglimit(i)
register int i;
{
#define NOISE	256

	if( i > NOISE )
		return( i-NOISE );
	if( i < -NOISE )
		return( i+NOISE );
	return(0);
}

/* MEGATEK is here, no need to fear */

#define BUFSIZE		10000		/* # of displaylist elements */
static displaylist_t	storage[BUFSIZE];/* Building area for solids */

/*
 *			M G _ C V T V E C S
 *
 * Convert model space vector list to Megatek displaylist, and
 * calculate displaylist memory requirement.
 * Returns # of bytes actually consumed in displaylist buffer.
 * The co-ordinate system extends from +full scale to
 * -full scale, as the object extends from xmin to xmax.
 * Hence the total co-ordinate distance is 2*full scale.
 *
 * Note that these macros draw the object FULL SCALE.
 * (Ie to occupy the full screen).  dozoom() depends on this.
 *
 *	delta * full scale / (scale/2)
 * gives delta * full scale * 2 / scale
 *
 * 4094 = 2047 * 2
 */
#define XMGCVT(x)		( (int) (((x) - sp->s_center[0]) * factor ))
#define YMGCVT(y)		( (int) (((y) - sp->s_center[1]) * factor ))
#define ZMGCVT(z)		( (int) (((z) - sp->s_center[2]) * factor ))

unsigned
Mg_cvtvecs( sp )
register struct solid *sp;
{
	register int curz;		/* Current z beam coordinate */
	register displaylist_t	*output;
	register int temp1;
	register struct veclist *vp;
	static float factor;
	unsigned count;
	int nvec;

	factor = 4094.0 / sp->s_size;	/* Conversion to -4094 .. +4094 */
	curz = 32767;			/* Undrawable point */
	output = &storage[0];

	if( sp->s_soldash )
		*output++ = MGI_SETDASH( 0xF5F5 );

	nvec = sp->s_vlen;
	for( vp = sp->s_vlist; nvec-- > 0; vp++ )  {
		if( vp->vl_pen == PEN_UP )  {
			/* Move, not draw */
			if( (temp1 = ZMGCVT( vp->vl_pnt[2] )) != curz ) {
				*output++ = MGI_Z_NEXT( temp1 );
				curz = temp1;
			}
			*output++ = MGI_A_MOVE(
				XMGCVT( vp->vl_pnt[0] ),
				YMGCVT( vp->vl_pnt[1] ) );
		}  else  {
			/* draw */
			if( (temp1 = ZMGCVT( vp->vl_pnt[2] )) != curz ) {
				*output++ = MGI_Z_NEXT( temp1 );
				curz = temp1;
			}
			*output++ = MGI_DRAW(
				XMGCVT( vp->vl_pnt[0] ),
				YMGCVT( vp->vl_pnt[1] ) );
		}
		if( output >= &storage[BUFSIZE-4] )  {
			(void)printf("Mg_cvtvecs:  Displaylist truncated\n");
			break;
		}
	}
	/* Finish off the display subroutine */
	*output++ = MGI_RET;

	count = (output - &storage[0]);
	count *= sizeof( displaylist_t );
	return( count );
}

/*
 * Loads displaylist from storage[]
 */
unsigned
Mg_load( addr, count )
unsigned addr, count;
{
	register int ret;
	if( addr ==  0 ) {
		(void)printf("Mg_load: addr=0\n");
		return( 0 );		/* FLAG:  error */
	}
	if( lseek( megfd, (long)addr, 0 ) < 0L )  {
		perror("Megatek displaylist lseek");
		return( 0 );		/* ERROR */
	}
	ret = write( megfd, (char *)&storage[0], count );
	if( ((unsigned)ret) != count ) {
		perror("Megatek displaylist write");
		(void)printf("Mg_load(%d,%d) fd=%d, ret=%d\n",
			addr, count, megfd, ret );
		return( 0 );		/* FLAG:  error */
	}
	return( count );
}

/* ARGSUSED */
void
Mg_statechange( a, b )
{
	/*
	 *  Based upon new state, possibly do extra stuff,
	 *  including enabling continuous tablet tracking,
	 *  or using kernel polling.
	 */
	switch( b )  {
	case ST_VIEW:
		/* Stop kernel data tablet polling */
		(void)ioctl( perfd, MGTPOLLOFF, 0 );
		break;
		
	case ST_S_PICK:
	case ST_O_PICK:
	case ST_O_PATH:
		/*
		 * Start kernel data tablet polling,
		 * because continuous updates are needed.
		 */
		(void)ioctl( perfd, MGTPOLLON, 0 );
		break;

	case ST_O_EDIT:
	case ST_S_EDIT:
		/* Stop kernel data tablet polling */
		(void)ioctl( perfd, MGTPOLLOFF, 0 );
		break;

	default:
		(void)printf("Ps_statechange: unknown state %s\n", state_str[b]);
		break;
	}
}

void
Mg_viewchange()
{
}

/*
 *  			M G _ C O L O R C H A N G E
 *  
 *  Go through the mater table, and compute nearest match to availible
 *  display manager colors.
 *  Sometime in the future, this probably should be a cluster analysis,
 *  given that we only have 16 colors to play with, and 5 pre-defined.
 */
static int slotsused;
void
Mg_colorchange()
{
	register struct mater *mp;

	slotsused = 0;
	for( mp = MaterHead; mp != MATER_NULL; mp = mp->mt_forw )
		mg_colorit( mp );

	color_soltab();		/* apply colors to the solid table */
	mg_loadcmap();
}


static int
mg_near(a,b)
int a,b;
{
	register int d;

	d = a-b;
	if( !d || d == (-1) || d == 1 )
		return(1);
	return(0);
}

int
mg_colorit( mp )
register struct mater *mp;
{
	register struct rgbtab *rgb;
	register int i;
	register int r,g,b;

	/* Limit values to 0,MINVAL..15 */
#define MINVAL		4
#define FOLD(x)		((x)>>4)
	if( (r = FOLD(mp->mt_r)) < MINVAL )  r = 0;
	if( (g = FOLD(mp->mt_g)) < MINVAL )  g = 0;
	if( (b = FOLD(mp->mt_b)) < MINVAL )  b = 0;
	if(mg_debug) (void)printf("%d,%d,%d mapped to %d,%d,%d: ", mp->mt_r, mp->mt_g, mp->mt_b, r,g,b);

	if( (r == 15 && g == 15 && b == 15) ||
	    (r == 0 && g == 0 && b == 0) )  {
		if(mg_debug) (void)printf("WHITE\n");
		mp->mt_dm_int = MGC_WHITE;
		return;
	}

	/* First, see if this matches an existing color map entry */
	for( i = 0; i < FIRSTFREESLOT+slotsused; i++ )  {
		rgb = &mg_rgbtab[i];
		if( rgb->r == r && rgb->g == g && rgb->b == b )  {
		if(mg_debug) (void)printf("matches [%d]\n", i);
			 mp->mt_dm_int = i;
			 return;
		}
	}

	/* Now try for "loose" match to an existing color */
	for( i = 0; i < FIRSTFREESLOT+slotsused; i++ )  {
		rgb = &mg_rgbtab[i];
		if( mg_near(rgb->r, r) &&
		    mg_near(rgb->g, g) &&
		    mg_near(rgb->b, b) )  {
			if(mg_debug) (void)printf("nearly %d\n", i);
			 mp->mt_dm_int = i;
			 return;
		}
	}

	/* If slots left, create a new color map entry, first-come basis */
	if( slotsused < NSLOTS )  {
		rgb = &mg_rgbtab[i=(FIRSTFREESLOT+slotsused++)];
		rgb->r = r;
		rgb->g = g;
		rgb->b = b;
		mp->mt_dm_int = i;
		if(mg_debug) (void)printf("new slot %d\n", i);
		return;
	}

	/* Default color */
	mp->mt_dm_int = MGC_YELLOW;
	if(mg_debug) (void)printf("defaults to yellow\n");
}

void
Mg_debug(lvl)
{
	mg_debug = lvl;
}

void
Mg_window(w)
int w[];
{
	register int i;
	for( i=0; i<6; i++ )
		mg_window[i] = w[i];
}
