ftp.nice.ch/pub/next/graphics/3d/geomview.1.4.1.s.tar.gz#/Geomview/src/lib/mg/common/mg.c

This is mg.c in view mode; [Download] [Up]

/* Copyright (c) 1992 The Geometry Center; University of Minnesota
   1300 South Second Street;  Minneapolis, MN  55454, USA;
 
This file is part of geomview/OOGL. geomview/OOGL is free software;
you can redistribute it and/or modify it only under the terms given in
the file COPYING, which you should have received along with this file.
This and other related software may be obtained via anonymous ftp from
geom.umn.edu; email: software@geom.umn.edu. */
static char *copyright = "Copyright (C) 1992 The Geometry Center";

/* Authors: Charlie Gunn, Stuart Levy, Tamara Munzner, Mark Phillips */

/*
 * $Id: mg.c,v 1.56 1993/09/09 22:04:19 slevy Exp $
 * Machine-independent part of MG library.
 * Initialization, common code, and some mgcontext maintenance.
 *
 * These are the common versions of MG functions (see mg.doc for
 * details).  The header comment for each common function includes an
 * entry labeled "DEVICE USE" which describes the way in which the
 * device version of the same function is expected to call the common
 * version.
 *
 * See mg.doc for more information about these functions.
 */

#include "mgP.h"

extern struct mgfuncs mgnullfuncs;	/* Forward */

mgcontext *_mgc = NULL;
mgcontext *_mgclist = NULL;

static struct mgastk *mgafree = NULL;
static struct mgxstk *mgxfree = NULL;

#define	MGC	_mgc

/*
 * mgcurrentcontext() returns a pointer to the currently-selected context,
 * for use in a later call to mgctxselect().
 * Returns NULL if no context is selected.
 */
mgcontext *
mgcurrentcontext()
{
    return _mgc;
}


/*-----------------------------------------------------------------------
 * Function:	mgdevice_NULL
 * Description:	select the NULL device as the current MG device
 * Returns:	1
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 10:36:21 1991
 * Notes:	
 */
int
mgdevice_NULL()
{
    if(MGC != NULL && MGC->devno != MGD_NULL)
	MGC = NULL;
    _mgf = mgnullfuncs;
    return 1;
}

/*-----------------------------------------------------------------------
 * Function:	mg_newcontext
 * Description:	initialize an mgcontext structure
 * Args:	mgc: ptr to context structure to initialize
 * Returns:	mgc
 * Author:	slevy (doc by mbp)
 * Date:	Wed Sep 18 16:42:52 1991
 * DEVICE USE:	required --- mgxx_ctxcreate() should call this immediately
 *		after allocating a new mgcontext structure.
 * Notes:	Further device-specific initialization is normally required.
 */
mgcontext *
mg_newcontext(mgc)
    register mgcontext *mgc;
{
    bzero((char *)mgc, sizeof(*mgc));
    RefInit((Ref *)mgc, MGCONTEXTMAGIC);
    mgc->shown = 1;
    mgc->win = WnCreate(WN_NAME, "minnegraphics", WN_END);
    mgc->cam = CamCreate( CAM_END );
    mgc->background.r = 0.0;
    mgc->background.g = 0.0;
    mgc->background.b = 0.0;
    mgc->background.a = 1.0;
    {
	register struct mgastk *ma;

	mgc->astk = ma = OOGLNewE(struct mgastk, "mg appearance stack");
	bzero((char *)ma, sizeof(*ma));	/* Sets next = NULL, *_seq = 0 */
	MtDefault(&(ma->mat));
	LmDefault(&(ma->lighting));
	ApDefault(&(ma->ap));
	ma->ap.mat = &(ma->mat);
	ma->ap.lighting = &(ma->lighting);
    }
    {
	register struct mgxstk *mx;

	mgc->xstk = mx = OOGLNewE(struct mgxstk, "mg transform stack");
	mx->next = NULL;
	TmIdentity(mx->T);
	mx->xfm_seq = mx->hasinv = 0;
    }
    mgc->opts = MGO_HIDDEN|MGO_DOUBLEBUFFER;
    mgc->devno = MGD_NODEV;	/* Device-specific init should change this */

    TmIdentity(mgc->W2C); TmIdentity(mgc->C2W);
    TmIdentity(mgc->W2S); TmIdentity(mgc->S2W);
    TmIdentity(mgc->O2S); TmIdentity(mgc->S2O);

    TmIdentity(mgc->T4);
    mgc->T4_seq = 0;

    mgc->space = TM_EUCLIDEAN;

    mgc->NDinfo = NULL;
    VVINIT(mgc->point, HPoint3, 7);

    mgc->next = _mgclist;
    _mgclist = mgc;

    return mgc;
}

int
mg_appearancebits( Appearance *ap, int mergeflag, int *valid, int *flag )
{
  register Appearance *dst;

  if (!_mgc->astk) {
    OOGLError(0,"mg_appearanceflags: no global context");
    return 0;
  }
  dst = &(_mgc->astk->ap);

  /* Mask of fields to change in dst */  
  if (ap == NULL) {
    *valid = dst->valid;
    *flag = dst->flag;
    mergeflag = MG_MERGE;
  } else {
    *valid = ap->valid;
    *flag = ap->flag;
  }
  if (mergeflag == MG_MERGE) {
    *valid &= ~dst->override;
  }
  return 1;
}

/*-----------------------------------------------------------------------
 * Function:	mg_identity
 * Description:	Set the current object xform to the identity
 * Args:	(none)
 * Returns:	nothing
 * Author:	slevy (doc by mbp)
 * Date:	Wed Sep 18 16:46:06 1991
 * Notes:	Sets the xform on the top of the current context's xform
 *		stack to the identity.  Also sets the MC_TRANS bit of
 *		the context's "changed" flag and increment's the current xfm
 *		sequence number.
 * DEVICE USE:  optional --- if the device actually uses the context
 *		structure's xform stack, call this to do the work.  If
 *		the device keeps its own stack, it doesn't have to call
 *		this.
 */
void
mg_identity( void )
{
    TmIdentity(_mgc->xstk->T);
    _mgc->changed |= MC_TRANS;
    _mgc->xstk->xfm_seq++;
}

/*-----------------------------------------------------------------------
 * Function:	mg_settransform
 * Description:	Set the current object xform
 * Args:	T
 * Returns:	nothing
 * Author:	slevy (doc by mbp)
 * Date:	Wed Sep 18 16:46:06 1991
 * Notes:	Sets the xform on the top of the current context's xform
 *		stack to T.  Also sets the MC_TRANS bit of
 *		the context's "changed" flag and increment's the current xfm
 *		sequence number.
 * DEVICE USE:  optional --- if the device actually uses the context
 *		structure's xform stack, call this to do the work.  If
 *		the device keeps its own stack, it doesn't have to call
 *		this.
 */
void
mg_settransform( Transform T )
{
    TmCopy(T, _mgc->xstk->T);
    _mgc->changed |= MC_TRANS;
    _mgc->xstk->xfm_seq++;
}

/*-----------------------------------------------------------------------
 * Function:	mg_gettransform
 * Description:	Get the current object xform
 * Args:	T
 * Returns:	nothing
 * Author:	slevy (doc by mbp)
 * Date:	Wed Sep 18 16:46:06 1991
 * Notes:	Writes the current object xform, from the top of the
 *		context's xform stack, into T.
 * DEVICE USE:  optional --- if the device actually uses the context
 *		structure's xform stack, call this to do the work.  If
 *		the device keeps its own stack, it doesn't have to call
 *		this.
 */
void
mg_gettransform( Transform T )
{
    TmCopy(_mgc->xstk->T, T);
}

/*-----------------------------------------------------------------------
 * Function:	mg_transform
 * Description:	premultiply the current object xform by a transform
 * Args:	T: the transform to premultiply by
 * Returns:	nothing
 * Author:	slevy (doc by mbp)
 * Date:	Wed Sep 18 16:46:06 1991
 * Notes:	If X is the context's current object xform, replaces X
 *		by T X.
 * DEVICE USE:  optional --- if the device actually uses the context
 *		structure's xform stack, call this to do the work.  If
 *		the device keeps its own stack, it doesn't have to call
 *		this.
 */
void
mg_transform( Transform T )
{
    TmConcat(T, _mgc->xstk->T, _mgc->xstk->T);
    _mgc->changed |= MC_TRANS;
    _mgc->xstk->xfm_seq++;
    _mgc->xstk->hasinv = 0;
}

/*-----------------------------------------------------------------------
 * Function:	mg_pushappearance
 * Description:	push the context's appearance stack
 * Returns:	nothing
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 10:37:55 1991
 * Notes:	
 * DEVICE USE:	required --- all devices must maintain this stack
 */
int
mg_pushappearance()
{
    register struct mgastk *ma;

    if(mgafree) ma = mgafree, mgafree = ma->next;
    else ma = OOGLNew(struct mgastk);
    *ma = *_mgc->astk;
    ma->next = _mgc->astk;
    ma->lighting.lights = LtCopylist(_mgc->astk->lighting.lights,1);
    ma->ap.lighting = &(ma->lighting);
    ma->ap.mat = &(ma->mat);
    _mgc->astk = ma;
}

/*-----------------------------------------------------------------------
 * Function:	mg_popappearance
 * Description:	pop the context's appearance stack
 * Returns:	nothing
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 10:51:12 1991
 * Notes:	
 * DEVICE USE:	required --- all deviced must maintain this stack
 */
int
mg_popappearance()
{
    register struct mgastk *mp;
    register struct mgcontext *ms = _mgc;

    mp = ms->astk->next;
    if(mp == NULL)
	return -1;
    if(ms->astk->ap_seq != mp->ap_seq) ms->changed |= MC_AP;
    if(ms->astk->mat_seq != mp->mat_seq) ms->changed |= MC_MAT;
    if(ms->astk->light_seq != mp->light_seq) ms->changed |= MC_LIGHT;
    LtDeletelist(ms->astk->lighting.lights);
    ms->astk->next = mgafree; mgafree = ms->astk;
    ms->astk = mp;
    return 0;
}

/*
 * Transform light(s) to global coordinate system, if they aren't already.
 * Transforms them in place; this is safe, since mg keeps a private copy of
 * the light structures.
 */
void
mg_globallights( LtLight *lights, int worldbegin )
{
    LtLight *lt;

    for(lt = lights; lt != NULL; lt = lt->next) {
	switch(lt->location) {
	case LTF_GLOBAL:
		lt->globalposition = lt->position;
		break;
	case LTF_CAMERA:
		HPt3Transform(_mgc->C2W, &lt->position, &lt->globalposition);
		if (worldbegin) lt->changed = 1;
		break;
	case LTF_LOCAL:
		HPt3Transform(_mgc->xstk->T, &lt->position, &lt->position);
		lt->globalposition = lt->position;
		lt->location = LTF_GLOBAL;
		if (worldbegin) lt->changed = 1;
		break;
	}
    }
}

/*-----------------------------------------------------------------------
 * Function:	mg_setappearance
 * Description:	Operate on appearance in current context
 * Args:	*ap: the appearance to assign or merge
 *		mergeflag: MG_MERGE or MG_SET
 * Returns:	ptr to current appearance
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 10:59:25 1991
 * Notes:	Modifies the context's current apperance.  Does not
 *		modify *ap.
 *		  mergeflag = MG_MERGE: merge *ap into current appearance
 *		  mergeflag = MG_SET: set current appearance to *ap
 * DEVICE USE:  required --- when ???
 *
 *		Can we modify this to do some of the flag setting
 *		than mggl_setappearance currently does???  This
 *		seems common to all devices.
 */
Appearance *
mg_setappearance( Appearance *ap, int mergeflag )
{
    register Appearance *nap;
    register struct mgastk *ma = _mgc->astk;

    if(mergeflag == MG_MERGE) {
	nap = ApMerge(ap, &ma->ap, 1);	/* Merge, in place */
	ma->changed |= MC_AP;
	ma->ap = *nap;
	/* Assign mat and light too? */
    } else {
	ApCopy(ap, &ma->ap);
	ma->changed |= MC_AP | MC_MAT | MC_LIGHT;
    }
    if(ap->lighting)
	mg_globallights(ma->lighting.lights, 0);
    return &_mgc->astk->ap;
}

/*-----------------------------------------------------------------------
 * Function:	mg_getappearance
 * Description:	get the current appearance
 * Returns:	ptr to the current appearance
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 11:08:06 1991
 * DEVICE USE:	optional
 * Notes:	The pointer returned points to the context's private copy
 *		of the appearance.  Don't modify it!
 *
 *		Should we allow this?  Or should this copy the appearance
 *		to an address passed as an argument ???
 */
Appearance *
mg_getappearance()
{
  return( &(_mgc->astk->ap) );
}


/*-----------------------------------------------------------------------
 * Function:	mg_setcamera
 * Description:	Set the context's camera
 * Args:	*cam: the camera to use
 * Returns:	nothing
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 11:16:46 1991
 * Notes:	The context does not maintain an internal copy of the
 *		camera.  Only the pointer is stored.
 * DEVICE USE:  required
 */
int
mg_setcamera( Camera *cam )
{
  RefIncr((Ref *)cam); /* Incr count first: allow setting same camera */
  CamDelete(_mgc->cam);
  _mgc->cam = cam;
  _mgc->changed |= MC_CAM;
}

/*-----------------------------------------------------------------------
 * Function:	mg_ctxset
 * Description:	set some attributes in the current context
 * Args:	attr, ...: list of attribute-value pairs, terminated
 *		  by MG_END
 * Returns:	nothing
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 11:22:28 1991
 * Notes:	DO NOT CALL THIS (yet)!  It currently does nothing.
 * DEVICE USE:  forbidden --- devices have their own mgxx_ctxset()
 *
 *		This needs to be modified to work as the NULL device.
 *		Use by other devices may never be needed.
 */
void
mg_ctxset( int attr, ... /*, MG_END */ )
{}


/*-----------------------------------------------------------------------
 * Function:	mg_ctxget
 * Description:	get an attribute from the current context
 * Args:	attr: the attribute to get
 *		*valuep: place to write attr's value
 * Returns:	???
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 11:26:49 1991
 * Notes:	DO NOT CALL THIS (yet)!  It currently does nothing.
 * DEVICE USE:  forbidden --- devices have their own mgxx_ctxget()
 *
 *		This needs to be modified to work as the NULL device.
 *		Use by other devices may never be needed.
 */
int
mg_ctxget( int attr, void *valuep )
{
  return -1;
}


/*-----------------------------------------------------------------------
 * Function:	mg_feature
 * Description:	determine whether the NULL device has a particular feature
 * Args:	feature: feature to test for
 * Returns:	-1 (means feature is not present)
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 11:29:51 1991
 * Notes:	NULL device is rather featureless at present, :-)
 * DEVICE USE:  forbidden --- devices have their own mgxx_feature()
 */
int
mg_feature( int feature )
{
  return -1;
}

/*-----------------------------------------------------------------------
 * Function:	mg_ctxcreate
 * Description:	create a new MG context for the NULL device
 * Args:	a1, ...: list of attribute-value pairs
 * Returns:	ptr to new context
 * Author:	mbp
 * Date:	Thu Sep 19 11:35:42 1991
 * Notes:	DO NOT CALL THIS --- it currently does nothing
 *		needs to be modified to work with NULL device ???
 * DEVICE USE:  forbidden --- devices have their own mgxx_ctxcreate(()
 */
mgcontext *
mg_ctxcreate( int a1, ... )
{
  return NULL;
}

/*-----------------------------------------------------------------------
 * Function:	mg_ctxdelete
 * Description:	delete an MG context for the NULL device
 * Args:	*ctx: ptr to context to delete
 * Returns:	nothing
 * Author:	mbp
 * Date:	Thu Sep 19 11:38:50 1991
 * Notes:	DO NOT CALL THIS --- it currently does nothing
 *		needs to be modified to work with NULL device ???
 * DEVICE USE:  forbidden --- devices have their own mgxx_ctxdelete()
 */
void
mg_ctxdelete( mgcontext *ctx )
{
    register struct mgcontext **mp;

    if(ctx == NULL)
	return;
    for(mp = &_mgclist; *mp != NULL; mp = &(*mp)->next) {
	if(*mp == ctx) {
	    *mp = ctx->next;
	    break;
	}
    }
    /* Free other data here someday XXX */
    OOGLFree(ctx);
}

/*-----------------------------------------------------------------------
 * Function:	mg_ctxselect
 * Description:	select the current context
 * Args:	*ctx: the context to select
 * Returns:	0 (why ???)
 * Author:	slevy (doc by mbp)
 * Date:	Thu Sep 19 11:40:15 1991
 * DEVICE USE:  required --- mgxx_ctxselect() should call this if
 *		the context to switch to if of a different device.
 *		This procedure then does the switch.
 */
int
mg_ctxselect( mgcontext *ctx )
{
   if(ctx != NULL && _mgf.mg_devno != ctx->devno) {
	/*
	 * For another device.
	 * Install that device's function pointers, and
	 * call its selectcontext routine.
	 */
	(*ctx->devfuncs->mg_setdevice)();
	mgctxselect(ctx);
   }
   _mgc = ctx;
   return 0;
}

/*-----------------------------------------------------------------------
 * Function:	mg_pushtransform
 * Description:	push the context xform stack
 * Returns:	nothing
 * Author:	mbp
 * Date:	Thu Sep 19 12:23:26 1991
 * DEVICE USE:	optional --- use if device actually uses our stack
 */
int
mg_pushtransform( void )
{
  register struct mgxstk *xfm;
  if(mgxfree) xfm = mgxfree, mgxfree = xfm->next;
  else xfm = OOGLNewE(struct mgxstk, "mgpushtransform");
  *xfm = *MGC->xstk;
  xfm->next = MGC->xstk;
  MGC->xstk = xfm;
}

/*-----------------------------------------------------------------------
 * Function:	mg_poptransform
 * Description:	pop the context xform stack
 * Returns:	nothing
 * Author:	mbp
 * Date:	Thu Sep 19 12:23:51 1991
 * DEVICE USE:	optional --- use if device actually uses our stack
 */
int
mg_poptransform( void )
{ register struct mgxstk *xfm = MGC->xstk;
  if(xfm->next == NULL)
    return -1;
  MGC->xstk = xfm->next;
  xfm->next = mgxfree;
  mgxfree = xfm;
  _mgc->has = 0;
}


/*
 * Handle 4D->3D transform
 * This allows the drawing routines to compute normals on 4D objects
 * by knowing how they'll appear in 3D, and also to know whether
 * the calculation must be redone (because the 4D->3D transform changed
 * since the normals were last computed).  This transform doesn't really
 * belong in the mg state, but the drawing routines need it, so this is the
 * natural place to put the data.
 */
mgset4to3( Transform T, int seq )
{
    TmCopy(T, MGC->T4);
    MGC->T4_seq = seq;
}

int
mgget4to3( Transform T )
{
    TmCopy(MGC->T4, T);
    return MGC->T4_seq;
}

void
mg_sync( void )
{}

void
mg_worldbegin( void )
{
  Transform S, V;
  WnPosition vp;

  CamGet(_mgc->cam, CAM_W2C, _mgc->W2C);
  CamGet(_mgc->cam, CAM_C2W, _mgc->C2W);
  CamView(_mgc->cam, V);
  WnGet(_mgc->win, WN_VIEWPORT, &vp);
	/* V maps world to [-1..1],[-1..1],[-1..1] */
  TmTranslate(S, 1., 1., 0.);
  TmConcat(V, S, V); /* now maps to [0..2],[0..2],[-1..1] */
  TmScale(S, .5*(vp.xmax-vp.xmin+1), .5*(vp.ymax-vp.ymin+1), 1.);
			/* now maps to [0..xsize],[0..ysize],[-1..1] */
  TmConcat(V, S, _mgc->W2S);	/* final world-to-screen matrix */
  TmInvert(_mgc->W2S, _mgc->S2W);	/* and screen-to-world */
  TmCopy(_mgc->W2S, _mgc->O2S);
  TmCopy(_mgc->S2W, _mgc->S2O);
  TmIdentity(_mgc->xstk->T);
  _mgc->xstk->hasinv = 0;
  _mgc->has |= HAS_S2O;
}

void
mg_findO2S()
{
    TmConcat(_mgc->xstk->T, _mgc->W2S, _mgc->O2S);
}

void
mg_findS2O()
{
  if(!(_mgc->has & HAS_S2O)) {
    if(!_mgc->xstk->hasinv) {
	TmInvert(_mgc->xstk->T, _mgc->xstk->Tinv);
	_mgc->xstk->hasinv = 1;
    }
    TmConcat(_mgc->S2W, _mgc->xstk->Tinv, _mgc->S2O);
    TmConcat(_mgc->xstk->T, _mgc->W2S, _mgc->O2S);
    _mgc->has |= HAS_S2O;
  }
}

/* Construct a prototype polygonal outline for creating fat points.
 * Curiously, we can do this independently of the position of the point,
 * if we operate in homogeneous space.
 */
void mg_makepoint()
{
  int i, n;
  float t, r, c, s;
  register HPoint3 *p;
  static float nsides = 3.0;

  if(!(_mgc->has & HAS_S2O))
    mg_findS2O();

  if(_mgc->astk->ap.linewidth <= 3) n = 4;
  else n = nsides * sqrt((double)_mgc->astk->ap.linewidth);
  vvneeds(&_mgc->point, n);
  VVCOUNT(_mgc->point) = n;
  r = _mgc->astk->ap.linewidth;
  for(i = 0, p = VVEC(_mgc->point, HPoint3);  i < n;  i++, p++) {
    t = 2*M_PI*i/n; s = r * sin(t); c = r * cos(t);
    p->x = _mgc->S2O[0][0]*c + _mgc->S2O[1][0]*s;
    p->y = _mgc->S2O[0][1]*c + _mgc->S2O[1][1]*s;
    p->z = _mgc->S2O[0][2]*c + _mgc->S2O[1][2]*s;
    p->w = _mgc->S2O[0][3]*c + _mgc->S2O[1][3]*s;
  }
  _mgc->has |= HAS_POINT;
}

void
mg_findcam()
{
  Transform inv;
  /*
   * Figure out where the camera is in the current coordinate system
   */
  if(!_mgc->xstk->hasinv) {
    TmInvert(_mgc->xstk->T, _mgc->xstk->Tinv);
    _mgc->xstk->hasinv = 1;
  }
  HPt3TransPt3(_mgc->xstk->Tinv, &_mgc->C2W[3][0], &_mgc->cpos);
  _mgc->has |= HAS_CPOS;
}

void
mg_worldend( void )
{}

void
mg_reshapeviewport( void )
{}

void
mg_polygon( int nv, Point3 *v, int nn,
	     Point3 *n, int nc,ColorA *c )
{}


void
mg_polylist()
{}


void
mg_mesh( int wrap,int nu,int nv, HPoint3 *p,
	  Point3 *n,ColorA *c )
{}

void
mg_line( HPoint3 *p1, HPoint3 *p2 )
{}

void
mg_polyline( int nv, HPoint3 *verts,
	      int nc, ColorA *colors )
{}

void
mg_quads( int nquads, HPoint3 *verts, Point3 *normals, ColorA *colors )
{
    int i;
    HPoint3 *v = verts;
    Point3 *n = normals;
    ColorA *c = colors;
    int dn = normals ? 4 : 0;
    int dc = colors ? 4 : 0;
    
    for(i = 0; i < nquads; i++, v += 4, n += dn, c += dc)
	mgpolygon(4, v, dn, n, dc, c);
}

void
mg_bezier(int du, int dv, int dimn, float *ctrlpts, float *txmapst, ColorA *c)
{
}

#define NULLFUNCS {					\
	MGD_NODEV,					\
	mgdevice_NULL,		/* mg_setdevice	       */ \
	mg_feature,		/* mg_feature	       */ \
(mgcontext *(*)())mg_ctxcreate,		/* mg_ctxcreate	       */ \
	mg_ctxdelete,		/* mg_ctxdelete	       */ \
(void (*)())mg_ctxset,		/* mg_ctxset	       */ \
	mg_ctxget,		/* mg_ctxget	       */ \
	mg_ctxselect,		/* mg_ctxselect	       */ \
	mg_sync,		/* mg_sync	       */ \
	mg_worldbegin,		/* mg_worldbegin       */ \
	mg_worldend,		/* mg_worldend	       */ \
	mg_reshapeviewport,	/* mg_reshapeviewport  */ \
	mg_settransform,	/* mg_settransform     */ \
	mg_gettransform,	/* mg_gettransform     */ \
	mg_identity,		/* mg_identity	       */ \
	mg_transform,		/* mg_transform	       */ \
	mg_pushtransform,	/* mg_pushtransform    */ \
	mg_poptransform,	/* mg_poptransform     */ \
	mg_pushappearance,	/* mg_pushappearance   */ \
	mg_popappearance,	/* mg_popappearance    */ \
	mg_setappearance,	/* mg_setappearance    */ \
	mg_getappearance,	/* mg_getappearance    */ \
	mg_setcamera,		/* mg_setcamera	       */ \
	mg_polygon,		/* mg_polygon	       */ \
	mg_polylist,		/* mg_polylist	       */ \
	mg_mesh,		/* mg_mesh	       */ \
	mg_line,		/* mg_line	       */ \
	mg_polyline,		/* mg_polyline	       */ \
	mg_quads,		/* mg_quads	       */ \
	mg_bezier,		/* mg_bezier */		\
	}

struct mgfuncs mgnullfuncs =	/* mgfuncs for null (default) output device */
  NULLFUNCS;

struct mgfuncs _mgf = NULLFUNCS;

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.