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

This is appearance.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 */

#include "appearance.h"

#define MAXSHININESS 1024.0

extern LmLighting * _LmSet(LmLighting *, int attr1, va_list *); /* Forward */
Appearance *ApLoad(Appearance *into, char *fname);
Material * _MtSet(Material *, int attr1, va_list *);	/* Forward */

Appearance *
_ApSet(Appearance *ap, int attr1, register va_list *alist)
{
    long mask;
    Color *co;
    int attr;
    char **ablock = NULL;

#define NEXT(type) OOGL_VA_ARG(type,alist,ablock)
	
    if (ap == NULL) {
        /*
         * New Appearance created here.
         */
	ap = OOGLNewE(Appearance, "ApCreate Appearance");
	ApDefault(ap);
    }

    for ( attr = attr1; attr != AP_END; attr = NEXT(int)) {
	switch (attr) { /* parse argument list */
	  case AP_ABLOCK:
	    ablock = NEXT(char **);
	    break;
	  case AP_DO:
	    mask = NEXT(int);
	    ap->flag |= mask;
	    ap->valid |= mask;
	    break;
	  case AP_DONT:
	    mask = NEXT(int);
	    ap->flag &= ~mask;
	    ap->valid |= mask;
	    break;
	  case AP_INVALID:
	    ap->valid &= ~NEXT(int);
	    break;
	  case AP_OVERRIDE:
	    ap->override |= NEXT(int);
	    break;
	  case AP_NOOVERRIDE:
	    ap->override &= ~NEXT(int);
	    break;
	  case AP_MAT:
	    ap->mat = NEXT(Material*);
	    break;
	  case AP_MtSet:
	    ap->mat = ablock ? MtSet(ap->mat, MT_ABLOCK, ablock)
	      : _MtSet(ap->mat, va_arg(*alist, int), alist);
	    break;
	  case AP_LGT:
	    ap->lighting = NEXT(LmLighting*);
	    break;
	  case AP_LmSet:
	    if (!ap->lighting) ap->lighting = LmCreate(LM_END);
	    ap->lighting = ablock ? LmSet(ap->lighting, LM_ABLOCK, ablock)
	      : _LmSet(ap->lighting, va_arg(*alist, int), alist);
	    break;
	  case AP_NORMSCALE:
	    ap->nscale = NEXT(double);
	    ap->valid |= APF_NORMSCALE;
	    break;
	  case AP_LINEWIDTH:
	    ap->linewidth = NEXT(int);
	    ap->valid |= APF_LINEWIDTH;
	    break;
	  case AP_SHADING:
	    /* should be APF_{CONSTANT,FLAT,SMOOTH} */
	    ap->shading = NEXT(int);
	    ap->valid |= APF_SHADING;
	    break;
	  default:
	    OOGLError (0, "_ApSet: undefined option: %d\n", attr);
	    return NULL;
	    break; 
	}
    }
    return ap;

#undef NEXT
}


Appearance *
ApCreate(int a1, ...)
{
    va_list alist;
    Appearance *ap;

    va_start(alist,a1);
    ap = _ApSet(NULL, a1, &alist);
    va_end(alist);
    return ap;
}


Appearance *
ApSet(Appearance *ap, int a1, ... )
{
    va_list alist;
    va_start(alist,a1);
    ap = _ApSet(ap, a1, &alist);
    va_end(alist);
    return ap;
}

int
ApGet(Appearance *ap, int attr, void *value)
{
    if (!ap) return -1;

    switch (attr) {
      case AP_DO:
      case AP_DONT:	*(int *) value =  ap->flag; break;
      case AP_OVERRIDE:
      case AP_NOOVERRIDE: *(int *) value = ap->override; break;
      case AP_VALID:
      case AP_INVALID:	*(int *) value = ap->valid; break;
      case AP_MAT:	*(Material **) value = ap->mat; break;
      case AP_LGT:	*(LmLighting **) value = ap->lighting; break;
      case AP_NORMSCALE: *(double *) value = ap->nscale; break;
      case AP_LINEWIDTH: *(int *) value = ap->linewidth; break;
      case AP_SHADING:	*(int *)value = ap->shading; break;
      default:
	OOGLError (0, "ApGet: undefined option: %d\n", attr);
	return -1;
	break; 
    }
    return attr;
}

void
ApDelete(Appearance *ap)
{
    if(ap == NULL || RefDecr((Ref *)ap) > 0)
	return;
    if (ap->mat) MtDelete(ap->mat);
    if (ap->lighting) LmDelete(ap->lighting);
    OOGLFree(ap);
}

/*
 * Copies just the Appearance part, not its Material and LmLighting children.
 * Pointers to the latter are retained BUT their reference counts are NOT
 * incremented.  The caller MUST either RefIncr() or reassign mat and lighting.
 */
Appearance *
ApCopyShallow( Appearance *ap, register Appearance *into )
{
    if(ap == NULL)
	return NULL;
    if(into == NULL) {
	into = OOGLNewE(Appearance, "ApCopy: Appearance");
	*into = *ap;
	into->mat = NULL;
	into->backmat = NULL;
	into->lighting = NULL;
	RefInit((Ref *)into, APMAGIC);
    } else {
	into->flag = ap->flag;
	into->valid = ap->valid;
	into->override = ap->override;
	into->nscale = ap->nscale;
	into->linewidth = ap->linewidth;
	into->shading = ap->shading;
    }
    return into;
}
    
Appearance *
ApCopy( Appearance *ap, register Appearance *into )
{
    if (ap == NULL) return into;
    into = ApCopyShallow( ap, into );
    if(ap->mat) into->mat = MtCopy(ap->mat, into->mat);
    if(ap->backmat) into->backmat = MtCopy(ap->backmat, into->backmat);
    if(ap->lighting) into->lighting = LmCopy(ap->lighting, into->lighting);
    return into;
}

/*
 * Merges appearance properties of src into dst.
 * If "inplace" is true, data are modified in place.
 * Otherwise if any modifications are needed to dst, a copy is made & returned.
 * If no modifications are necessary, the returned Appearance's reference
 * count is still incremented.
 * Thus, in all cases, the caller should ApDelete() the returned value
 * when finished with it.
 */
Appearance *
ApMerge( register Appearance *src, register Appearance *dst, int mergeflags )
{
    int mask;
    Material *mt, *oldmt, *bmt, *oldbmt;
    LmLighting *lts, *oldlts;
 

    if(dst == NULL)
	return ApCopy(src, NULL);

    if(src == NULL) {
	RefIncr((Ref *)dst);
	return dst;
    }
    /* Mask of fields to copy to dst */
    mask = (mergeflags & APF_OVEROVERRIDE) ? src->valid :
			    src->valid &~ ( dst->override &~ src->override);

    mt = MtMerge(src->mat, dst->mat, mergeflags);
    bmt = MtMerge(src->backmat, dst->backmat, mergeflags);
    lts = LmMerge(src->lighting, dst->lighting, mergeflags);
    if((mergeflags & APF_INPLACE)
		 || (mask == 0 && mt == dst->mat &&
			lts == dst->lighting && bmt == dst->backmat)) {
	/*
	 * No changes, or we're asked to merge in place.  Bump ref count.
	 */
	RefIncr((Ref *)dst);
    } else {
	/*
	 * Another special case: Copy appearance, don't copy the items already
	 * copied by {Mt,Lm}Merge.  We're about to overwrite these values, so
	 * toss the old ones.  Pretty kludgy, but what can we do?
	 */
	dst = ApCopyShallow(dst, NULL);
    }
    if(mt != dst->mat) MtDelete(dst->mat);
    if(bmt != dst->backmat) MtDelete(dst->backmat);
    if(lts != dst->lighting) LmDelete(dst->lighting);

    dst->mat = mt;
    dst->backmat = bmt;
    dst->lighting = lts;
    if(mask) {
	/* Merge together appearance-specific data */
	dst->flag = (src->flag & mask) | (dst->flag & ~mask);
	dst->valid = (src->valid & mask) | (dst->valid & ~mask);
	dst->override = (src->override & mask) | (dst->override & ~mask);
	if(mask & APF_NORMSCALE) dst->nscale = src->nscale;
	if(mask & APF_LINEWIDTH) dst->linewidth = src->linewidth;
	if(mask & APF_SHADING) dst->shading = src->shading;
    }
    return dst;
}

static struct {
	char *word;
	unsigned short amask;
	unsigned short aval;
} ap_kw[] = {
	{ "appearance",	0,		1 },
	{ "face",	APF_FACEDRAW,	0 },
	{ "edge",	APF_EDGEDRAW,	0 },
	{ "vect",	APF_VECTDRAW,	0 },
	{ "transparent", APF_TRANSP,	0 },
	{ "evert",	APF_EVERT,	0 },
	{ "shading",	APF_SHADING,	2 },
	{ "smooth",	APF_SHADING,	AP_SHADING + APF_SMOOTH },
	{ "flat",	APF_SHADING,	AP_SHADING + APF_FLAT },
	{ "constant",	APF_SHADING,	AP_SHADING + APF_CONSTANT },
	{ "csmooth",	APF_SHADING,	AP_SHADING + APF_CSMOOTH },
	{ "normal",	APF_NORMALDRAW,	0 },
	{ "normscale",	APF_NORMSCALE,  3 },
	{ "linewidth",	APF_LINEWIDTH,	4 },
	{ "material",	0,		5 },
	{ "backmaterial", 0,		7 },
	{ "light",	0,		6 },
	{ "lighting",	0,		6 },
};

Appearance *
ApFLoad( Appearance *into, FILE *stream, char *fname )
{
    register Appearance *ap;
    char *w;
    int i;
    int any = 0;
    int brack = 0;
    int over, not, value;
    int mask, flagmask;
    int more;
    int mine = 1;	/* Questionable -- we'll report all errors */

    ap = into;

    over = not = 0;  value = ~0;
    more = 0;
    do {
	(void) fnextc(stream, 0);

	switch(i = fgetc(stream)) {
	case '<': ap = ApLoad(ap, fdelimtok("{}()", stream, 0)); break;
	case EOF: brack = 0; break;
	case '{': brack++; break;
	case '}': if(brack-- <= 0) { ungetc(i, stream); } break;
	case '+': value = ~0; break;
	case '-': value = 0; break;
	case '!': not = 1; break;
	case '*': over = 1; break;

	default:
	    more = 0;
	    ungetc(i, stream);
	    w = fdelimtok("{}()", stream, 0);
	    if(w == NULL)
		break;
	    for(i = sizeof(ap_kw)/sizeof(ap_kw[0]); --i >= 0; )
		if(!strcmp(ap_kw[i].word, w))
		    break;
		
	    if(i < 0) {
		if(mine)
		    OOGLError(1,
			"ApFLoad: file %s: unknown appearance keyword %s", 
			fname, w);
		return NULL;
	    }
	    if(ap == NULL)
		ap = ApCreate(AP_END);

	    mask = flagmask = ap_kw[i].amask;
	    if(not) {
		switch(ap_kw[i].aval) {
		    case 5: MtDelete(ap->mat); ap->mat = NULL; break;
		    case 6: LmDelete(ap->lighting); ap->lighting = NULL; break;
		}
		ap->flag &= ~mask;
		if(!over) ap->valid &= ~mask;
		ap->override &= ~mask;
	    } else {
		switch(ap_kw[i].aval) {
		case 0: break;
		case 1: mine = more = 1; break;
		case 2: fgetni(stream, 1, &ap->shading, 0); break;
		case 3:
		    if(fgetnf(stream, 1, &ap->nscale, 0) <= 0)
			OOGLError(1,"ApFLoad: %s: \"normscale\": value expected",
				fname);
		    break;
		case 4:
		    if(fgetni(stream, 1, &ap->linewidth, 0) <= 0)
			OOGLSyntax(stream, "%s \"linewidth\": value expected",
				fname);
		    break;
		case 5:
		    if((ap->mat = MtFLoad(ap->mat, stream, fname)) == NULL)
			OOGLSyntax(stream,"Can't read material in %s",
				fname);
		    break;
		case 7:
		    if((ap->backmat = MtFLoad(ap->backmat, stream, fname)) == NULL)
			OOGLError(1,"Can't read backmaterial, file %s",
				fname);
		    break;
		case 6:
		    ap->lighting = LmFLoad(ap->lighting, stream, fname);
		    if(ap->lighting == NULL)
			OOGLError(1,"Can't read lighting, file %s",
				fname);
		    break;
		default:
		    if(ap_kw[i].aval >= AP_SHADING)
			ap->shading = ap_kw[i].aval - AP_SHADING;
		}
		if(value) ap->flag |= flagmask;
		else ap->flag &= ~flagmask;
		ap->valid |= mask;
		if(over) ap->override |= mask;
	    }
	    /* Reset for next keyword */
	    over = not = 0;  value = ~0;

	}
    } while(brack > 0 || more);
    return ap;
}

Appearance *
ApLoad(Appearance *into, char *fname)
{
    FILE *f = fopen(fname, "r");
    Appearance *a;

    if(f == NULL) {
	OOGLError(0, "ApLoad: can't open %s: %s", fname, sperror());
	return NULL;
    }
    a = ApFLoad(into, f, fname);
    fclose(f);
    return a;
}


void ApFSave( Appearance *ap, Handle *aphandle, FILE *f, char *fname )
{
    int valid;
    int mask;
    register int i;

    if(ap == NULL)
	return;

    valid = ap->valid;
    fprintf(f, "appearance {\n");

    for(i = 0; i < sizeof(ap_kw)/sizeof(ap_kw[0]); i++) {
	mask = ap_kw[i].amask;
	if((valid & mask) == 0)
	    continue;
	Apsavepfx(valid, ap->override, mask, "", f);
	if(ap_kw[i].aval == 0) {
	    if((mask & ap->flag) == 0)
		fputc('-', f);
	    fputs(ap_kw[i].word, f);
	}
	valid &= ~mask;
	switch(mask) {
	case APF_SHADING:
	    fputs(" shading ", f);
	    switch(ap->shading) {
	    case APF_SMOOTH:	fputs("smooth", f);	break;
	    case APF_FLAT:	fputs("flat", f);	break;
	    case APF_CONSTANT:	fputs("constant", f);	break;
	    case APF_CSMOOTH:   fputs("csmooth", f);	break;
	    default:		fprintf(f, "%d", ap->shading); break;
	    }
	    break;
	case APF_NORMSCALE:
	    fprintf(f, "  normscale %g", ap->nscale);
	    break;
	case APF_LINEWIDTH:
	    fprintf(f, "  linewidth %d ", ap->linewidth);
	    break;
	}
	fputc('\n', f);
    }

    if(ap->mat) {
	fprintf(f, "  material {\n");
	MtFSave(ap->mat, f);
	fprintf(f, "  }\n");
    }
    if(ap->backmat) {
	fprintf(f, "  backmaterial {\n");
	MtFSave(ap->backmat, f);
	fprintf(f, "  }\n");
    }
    if(ap->lighting) {
	fprintf(f, "  lighting {\n");
	LmFSave(ap->lighting, f, fname);
	fprintf(f, "  }\n");
    }
    fprintf(f, "}\n");
    return;
}

ApSave(Appearance *ap, char *fname)
{
    FILE *f = fname == NULL ? stdout : fopen(fname, "w");

    if(f) {
	ApFSave(ap, NULL, f, fname);
	if(fname != NULL)
	    fclose(f);
    }
}

Material *
_MtSet(Material *mat, int attr1, va_list *alist)
{
    Color *co;
    long mask;
    int attr;
    char **ablock = NULL;

#define NEXT(type) OOGL_VA_ARG(type,alist,ablock)
    
    if (mat == NULL) {
        /*
         * New Material created here.
         */
	mat = OOGLNewE(Material, "new Material"); 
	MtDefault(mat);
    }

    for(attr = attr1; attr != MT_END; attr = NEXT(int)) {
      switch (attr) { /* parse argument list */
      case MT_ABLOCK:
	ablock = NEXT(char**);
	break;
      case MT_AMBIENT:
	co = NEXT(Color *);
	CoCopy(co, &mat->ambient);
	mat->valid |= MTF_AMBIENT;
	break;
      case MT_DIFFUSE:
	co = NEXT(Color *);
	CoCopy(co, &mat->diffuse);
	mat->valid |= MTF_DIFFUSE;
	break;
      case MT_SPECULAR:
	co = NEXT(Color *);
	CoCopy(co, &mat->specular);
	mat->valid |= MTF_SPECULAR;
	break;
      case MT_EMISSION:
	co = NEXT(Color *);
	CoCopy(co, &mat->emission);
	mat->valid |= MTF_EMISSION;
	break;
      case MT_ALPHA: 
	mat->alpha = NEXT(double);
	mat->valid |= MTF_ALPHA;
	break;
      case MT_Ka: 
	mat->ka = NEXT(double);
	mat->valid |= MTF_Ka;
	break;
      case MT_Kd: 
	mat->kd = NEXT(double);
	mat->valid |= MTF_Kd;
	break;
      case MT_Ks: 
	mat->ks = NEXT(double);
	mat->valid |= MTF_Ks;
	break;
      case MT_SHININESS: 
	mat->shininess = NEXT(double);
	mat->valid |= MTF_SHININESS;
	break;
      case MT_EDGECOLOR:
	CoCopy(NEXT(Color *), &mat->edgecolor);
	mat->valid |= MTF_EDGECOLOR;
	break;
      case MT_NORMALCOLOR:
	CoCopy(NEXT(Color *), &mat->normalcolor);
	mat->valid |= MTF_NORMALCOLOR;
	break;
      case MT_INVALID:
	mat->valid &= ~NEXT(int);
	break;
      case MT_OVERRIDE:
	mat->override |= NEXT(int);
	break;
      case MT_NOOVERRIDE:
	mat->override &= ~NEXT(int);
	break;
      default:
	OOGLError (0, "_MtSet: undefined option: %d\n", attr);
	return NULL;
	break; 
      }
    }

    return mat;

#undef NEXT
}

Material *
MtCreate(int a1, ... )
{
    va_list alist;
    Material *mat;

    va_start(alist,a1);
    mat = _MtSet(NULL, a1, &alist);
    va_end(alist);
    return mat;
}

Material *
MtSet(Material *mat, int attr, ... )
{
    va_list alist;
    
    va_start(alist,attr);
    mat = _MtSet(mat,attr,&alist);
    va_end(alist);
    return mat;
}

int
MtGet(register Material *mat, int attr, void * value)
{
    if (mat == NULL)
	return -1;

    switch (attr) {
      case MT_AMBIENT:
	*(Color *) value = mat->ambient;
	break;
      case MT_DIFFUSE:
	*(Color *) value = mat->diffuse;
	break;
      case MT_SPECULAR:
	*(Color *) value = mat->specular;
	break;
      case MT_EMISSION:
	*(Color *) value = mat->emission;
	break;
      case MT_ALPHA: 
	*(double *) value = mat->alpha;
	break;
      case MT_Ka: 
	*(double *) value = mat->ka;
	break;
      case MT_Kd: 
	*(double *) value = mat->kd;
	break;
      case MT_Ks: 
	*(double *) value = mat->ks;
	break;
      case MT_SHININESS: 
	*(double *) value = mat->shininess;
	break;
      case MT_EDGECOLOR:
	*(Color *)value = mat->edgecolor;
	break;
      case MT_NORMALCOLOR:
	*(Color *)value = mat->normalcolor;
	break;
      case MT_OVERRIDE:
      case MT_NOOVERRIDE:
	*(int *) value = mat->override;
	break;
      case MT_VALID:
      case MT_INVALID:
	*(int *) value = mat->valid;
	break;
      default:
	OOGLError (0, "MtGet: undefined option: %d\n", attr);
	return -1;	
	break; 
    }
    
    return 1;
}

void
MtDelete(Material *mat)
{
    if(mat && RefDecr((Ref *)mat) <= 0) {
	mat->magic = -1;
	OOGLFree(mat);
    }
}

Material *
MtDefault( Material *mat )
{
  bzero(mat, sizeof(Material));
  RefInit((Ref *)mat, MATMAGIC);
  mat->valid = mat->override = 0;
  mat->alpha = 1.0;
  mat->Private = 0;
  mat->changed = 1;
}


Material *
MtCopy( Material *src, Material *dst )
{
  if (!src) return NULL;
    if(dst == NULL)
      dst = OOGLNewE(Material, "MtCopy: Material");
    *dst = *src;	
    dst->Private = 0;
    RefInit((Ref *)dst, MATMAGIC);
    dst->changed = 1;
    return dst;
}

#define max(a,b) (a)>(b)?(a):(b)

static
norm( color, coeff )
    Color *color;
    float *coeff;
{
    *coeff = max(color->r, color->g);
    *coeff = max(color->b, *coeff);

    if( *coeff != 0.0 ) {
	color->r /= *coeff;
	color->g /= *coeff;
	color->b /= *coeff;
    }
}

Material *
MtLoad(mat, name)
    Material *mat;
    char *name;
{
    FILE *f = fopen(name,"r");

    if(f == NULL) {
	OOGLError(0, "MtLoad: can't open %s: %s", name, sperror());
	return NULL;
    }
    mat = MtFLoad(mat, f, name);
    fclose(f);
    return mat;
}

/*
 * Load Material from file.
 * Syntax:
 *	< "filename_containing_material"	[or]
 *    {   keyword  value   keyword  value   ...  }
 *
 *   Each keyword may be prefixed by "*", indicating that its value should
 *   override corresponding settings in child objects.  [By default,
 *   children's appearance values supercede those of their parents.]
 *
 *  Note: don't overwrite ka, kd, ks if they're already set when we read in 
 *        the corresponding color.
 */
static char *mt_kw[] = {
  "shininess",	"ka",		"kd",		"ks",		"alpha",
  "emission",	"ambient",	"diffuse",	"specular",
  "edgecolor",	"normalcolor",	"material"
};
static unsigned short mt_flags[] = {
  MTF_SHININESS,  MTF_Ka,	MTF_Kd,		MTF_Ks,		MTF_ALPHA,
  MTF_EMISSION,   MTF_AMBIENT, MTF_DIFFUSE, MTF_SPECULAR,
  MTF_EDGECOLOR, MTF_NORMALCOLOR, 0
};
static char mt_args[] = { 1,1,1,1,1,  3,3,3,3,3,3, 0 };

Material *
MtFLoad(mat, f, fname)
    Material *mat;
    FILE *f;
    char *fname;	/* Used for error msgs, may be NULL */
{
    char *w;
    register int i;
    float v[3];
    int brack = 0;
    int over, not;
    int got;
    Material m;

    MtDefault(&m);

    over = not = 0;
    for(;;) {
	switch(fnextc(f, 0)) {
	case '<':
	    fgetc(f);
	    if(MtLoad(&m, fdelimtok("{}()", f, 0)) == NULL) return NULL;
	    if(!brack) goto done;
	    break;
	case '{': brack++; fgetc(f); break;
	case '}': if(brack) { fgetc(f); } goto done;
	case '*': over = 1; fgetc(f); break;		/* 'override' prefix */
	case '!': not = 1; fgetc(f); break;
	default:
	    w = fdelimtok("{}()", f, 0);
	    if(w == NULL)
		return MtCopy(&m, mat);
		/* break;	*/				/* done */

	    for(i = sizeof(mt_kw)/sizeof(mt_kw[0]); --i >= 0; )
		if(!strcmp(w, mt_kw[i]))
		    break;

	    if( i < 0) {
		OOGLError(1, "MtFLoad: %s: unknown material keyword %s",fname,w);
		return NULL;
	    } else if( !not && (got=fgetnf(f, mt_args[i], v, 0)) != mt_args[i] ) {
		OOGLError(1, "MtFLoad: %s: \"%s\" expects %d values, got %d",
		    fname, w, mt_args[i], got);
		return NULL;
	    }

	    if(not) {
		if(!over) m.valid &= ~mt_flags[i];
		m.override &= ~mt_flags[i];
	    } else {
		switch(i) {
		case 0: m.shininess = v[0]; break;
		case 1: m.ka = v[0]; break;
		case 2: m.kd = v[0]; break;
		case 3: m.ks = v[0]; break;
		case 4: m.alpha = v[0]; break;
		case 5: m.emission = *(Color *)v; break;
		case 6: m.ambient = *(Color *)v; break;
		case 7: m.diffuse = *(Color *)v; break;
		case 8: m.specular = *(Color *)v; break;
		case 9: m.edgecolor = *(Color *)v; break;
		case 10: m.normalcolor = *(Color *)v; break;
		}
		m.valid |= mt_flags[i];
		if(over) m.override |= mt_flags[i];
	    }
	    over = not = 0;
	}
    }
  done:
    return MtCopy(&m, mat);
}

/*
 * MtMerge(src, dst, mergeflags)
 * Merge Material values:  src into dst, controlled by flag.
 * If "inplace" is true, changes are made in dst itself; otherwise,
 * the dst material is copied if any changes need be made to it.
 * The returned Material's reference count is incremented as appropriate;
 * thus the caller should MtDelete() the returned Material when done.
 */
Material *
MtMerge(Material *src, Material *dst, int mergeflags)
{
    register int mask;

    if(dst == NULL)
	return MtCopy(src, NULL);

    /* Fields to merge in */
    mask = src ?
	(mergeflags & APF_OVEROVERRIDE) ?
	    src->valid : src->valid & ~(dst->override &~ src->override)
	: 0;

    if(mergeflags & APF_INPLACE)
	RefIncr((Ref *)dst);
    else
	dst = MtCopy(dst, NULL);

    if(mask == 0)			/* No changes to dst */
	return dst;

    dst->changed |= src->changed;
    dst->valid = (src->valid & mask) | (dst->valid & ~mask);
    dst->override = (src->override & mask) | (dst->override & ~mask);
    if(mask & MTF_EMISSION) dst->emission = src->emission;
    if(mask & MTF_AMBIENT) dst->ambient = src->ambient;
    if(mask & MTF_DIFFUSE) dst->diffuse = src->diffuse;
    if(mask & MTF_SPECULAR) dst->specular = src->specular;
    if(mask & MTF_Ka) dst->ka = src->ka;
    if(mask & MTF_Kd) dst->kd = src->kd;
    if(mask & MTF_Ks) dst->ks = src->ks;
    if(mask & MTF_ALPHA) dst->alpha = src->alpha;
    if(mask & MTF_SHININESS) dst->shininess = src->shininess;
    if(mask & MTF_EDGECOLOR) dst->edgecolor = src->edgecolor;
    if(mask & MTF_NORMALCOLOR) dst->normalcolor = src->normalcolor;
    return dst;
}

int
MtSave(mat,name)
    Material *mat;
    char *name;
{
    FILE *f;
    int ok;

    f = fopen(name,"w");
    if(!f) {
	perror(name);
	return -1;
    }
    ok = MtFSave(mat, f);
    fclose(f);
    return ok;
}

int
Apsavepfx(valid, override, mask, keyword, f)
    int valid, override, mask;
    FILE *f;
{
    if((valid & mask) == 0)
	return 0;
    fputc('\t', f);
    if(override & mask)
	fputc('*', f);
    fprintf(f, "%s ", keyword);
    return 1;
}


int
MtFSave(mat,f)
    register Material *mat;
    FILE *f;
{
    register int i;
    float v;
    Color *c;

    for(i = 0; i < sizeof(mt_kw)/sizeof(mt_kw[0]); i++) {
	if(Apsavepfx(mat->valid, mat->override, mt_flags[i], mt_kw[i], f)) {
	  switch(mt_flags[i]) {
	    case MTF_Ka: v = mat->ka; goto pfloat;
	    case MTF_Kd: v = mat->kd; goto pfloat;
	    case MTF_Ks: v = mat->ks; goto pfloat;
	    case MTF_SHININESS: v = mat->shininess; goto pfloat;
	    case MTF_ALPHA: v = mat->alpha; goto pfloat;
	  pfloat:
	    fprintf(f, "%f\n", v);
	    break;

	    case MTF_DIFFUSE: c = &mat->diffuse; goto pcolor;
	    case MTF_AMBIENT: c = &mat->ambient; goto pcolor;
	    case MTF_EMISSION: c = &mat->emission; goto pcolor;
	    case MTF_SPECULAR: c = &mat->specular; goto pcolor;
	    case MTF_EDGECOLOR: c = &mat->edgecolor; goto pcolor;
	    case MTF_NORMALCOLOR: c = &mat->normalcolor; goto pcolor;
	  pcolor:
	    fprintf(f, "%f %f %f\n", c->r, c->g, c->b);
	    break;
	  }
	}
    }
    return ferror(f);
}

void
MtPrint(mat)
    Material *mat;
{
    MtFSave(mat,stdout);
}


Material *
MtPlastic( 
    float Ka, float Kd, float Ks, float roughness,
    Color *Ca, Color *Cd, Color *Cs )
{
    Material *mat;
    static Color black = { 0.0, 0.0, 0.0 };
    mat = MtCreate( MT_END );
    mat->shininess = roughness < 1.0/MAXSHININESS ? 1.0/roughness : MAXSHININESS;
    mat->ka = Ka;
    mat->kd = Kd;
    mat->ks = Ks;
    mat->emission = black;
    if (Ca) mat->ambient = *Ca;
    if (Cd) mat->diffuse = *Cd;
    if (Cs) mat->specular = *Cs;
    return mat;
}

Appearance *
ApDefault(Appearance *ap)
{
  ap->valid = ap->override = 0;
  bzero(ap, sizeof(Appearance));
  RefInit((Ref *)ap, APMAGIC);
  ap->mat = NULL;
  ap->lighting = NULL;
  return ap;
}

void ApUseOverrides(Appearance *ap, int use_overrides)
{
  Material *mat;
  if(ap == NULL) return;
  ap->override = ap->valid & use_overrides;
  if((mat = ap->mat) != NULL) {
    mat->override = mat->valid & use_overrides;
    mat->changed = 1;
  }
  if((mat = ap->backmat) != NULL) {
    mat->override = mat->valid & use_overrides;
    mat->changed = 1;
  }
  if(ap->lighting) {
    ap->lighting->override = ap->lighting->valid & use_overrides;
    ap->lighting->changed = 1;
  }
}

/*
 * We assume dst is a child of src in the inheritance tree.
 * Erase all settings in dst that are defined in src,
 * so that src's settings can propagate to (the children of) dst.
 */
void ApLetPropagate(register Appearance *src, register Appearance *dst)
{
  if(src == NULL || dst == NULL) return;
  dst->valid &= ~src->valid;
  dst->override &= ~src->valid;
  if(dst->mat && src->mat) {
    dst->mat->valid &= ~src->mat->valid;
    dst->mat->override &= ~src->mat->valid;
    dst->mat->changed = 1;
  }
  if(dst->backmat && src->backmat) {
    dst->backmat->valid &= ~src->backmat->valid;
    dst->backmat->override &= ~src->backmat->valid;
    dst->backmat->changed = 1;
  }
  if(dst->lighting && src->lighting) {
    dst->lighting->valid &= ~src->lighting->valid;
    dst->lighting->override &= ~src->lighting->valid;
  }
}

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