#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "VPoly.h"
#include "../util/error.h"
#include "../util/memory.h"

#define VObjects_IMPORT
#include "VObjects.h"


struct entry {
	int       id;
	double    d;
	struct entry *next;
};



int
VComputeObjectAspect(VObject * obj, VPoint * loc)
{
	register int q;
	register double a, b, c, m;

	m = mag(loc->x, loc->y, loc->z);

	a = VDotProd(loc, &obj->xaxis);
	b = VDotProd(loc, &obj->yaxis) / m;
	c = VDotProd(loc, &obj->zaxis);

/*
 *  If the absolute angle between the Y axis and viewing vector is less than 30
 *  degrees, then it is either a right or left aspect.
 */

	if (b > 0.866) {
		return 1;
	}
	else if (b < -0.866) {
		return 0;
	}

	if (a < 0.0) {
/* front */
		if (b < 0.0) {
/* front right */
			if (c < 0.0) {
/* front right bottom */
				q = 2;
			}
			else {
/* front right top */
				q = 3;
			}
		}
		else {
/* front left */
			if (c < 0.0) {
/* front left bottom */
				q = 4;
			}
			else {
/* front left top */
				q = 5;
			}
		}
	}
	else {
/* aft */
		if (b < 0.0) {
/* aft right */
			if (c < 0.0) {
/* aft right bottom */
				q = 6;
			}
			else {
/* aft right top */
				q = 7;
			}
		}
		else {
/* aft left */
			if (c < 0.0) {
/* aft left bottom */
				q = 8;
			}
			else {
/* aft left top */
				q = 9;
			}
		}
	}

	return q;
}


VObject *
VExtrudeObject(VObject * obj, VPoint * vec)
{
#ifdef notdef
    VPoint tmp[4];
	int k, other;
#endif
    VPolygon *poly_tmp[65536];
    int i, j;
    VObject *newObj;
#ifdef FIXME_NOT_USED_CHECK_BELOW
	VPolygon *poly;
    VPoint scale = {1, 1, 1};
#endif

#ifdef DEBUG
    fprintf(stderr, "creating an extrusion based on \"%s\"\n", obj->name);
    fprintf(stderr, "starting with %d polygons\n", obj->numPolys);
#endif
    newObj = memory_allocate(sizeof(VObject), NULL);
    newObj->name = obj->name;
    newObj->extent = obj->extent;
    newObj->center = obj->center;

    for (i = 0; i < obj->numPolys; ++i) {
		if ((poly_tmp[obj->numPolys - i - 1] = VCopyPolygon(obj->polygon[i]))
			== (VPolygon *) NULL) {
			error_internal("VExtrudeObject: can't copy polygons", 0);
		}
    }

/*
 *  If clipping is enabled, then we should be reversing the vertices
 *  of this polygon
 */
    for (i = 0, j = obj->numPolys; i < obj->numPolys; ++i, ++j) {
		/* FIXME: to do */
		error_internal("FIXME", 0);
		#ifdef FIXME
		/*
			ERROR: missing 4-th argument of ScalePolygon():
		*/
		if ((poly_tmp[j] = ScalePolygon(obj->polygon[i], vec, &scale, 0.0))
			== (VPolygon *) NULL) {
			error_internal("VExtrudeObject: can't copy polygons", 0);
		}
		#endif
    }

/*
 *  Now create extrusion polygons by marching through the original polygon
 *  vertices and creating new polygons that connect the edges of the "upper"
 *  and "lower" polygons as we go.
 */

#ifdef notdef
    for (i = 0; i < obj->numPolys; ++i) {
		poly = obj->polygon[i];
		for (k = 0; k < poly->numVtces; ++k) {
			other = (k == 0) ? poly->numVtces - 1 : k - 1;
			tmp[0] = poly->vertex[other];
			tmp[1] = poly->vertex[k];
			tmp[2] = poly->vertex[k];
			tmp[2].x += vec->x;
			tmp[2].y += vec->y;
			tmp[2].z += vec->z;
			tmp[3] = poly->vertex[other];
			tmp[3].x += vec->x;
			tmp[3].y += vec->y;
			tmp[3].z += vec->z;
			poly_tmp[j++] = VCreatePolygonFromTemplate(4, tmp, poly);
		}
    }
#endif

/*
 *  Complete filling the object structure
 */

#ifdef DEBUG
    fprintf(stderr, "%d polygons in the extruded object\n", j);
#endif
    newObj->polygon = memory_allocate(sizeof(VPolygon *) * j, NULL);
    newObj->numPolys = j;
    for (i = 0; i < j; ++i) {
	newObj->polygon[i] = poly_tmp[i];
    }
    return newObj;
}


void
VDestroyObject(VObject *obj)
{
	long i;
	for (i=0; i<obj->numPolys; ++i) {
		VDestroyPolygon (obj->polygon[i]);
	}
	memory_dispose(obj->order);
	memory_dispose(obj->polygon);
	memory_dispose(obj->name);
	memory_dispose(obj);
}


VObject  *
VCopyObject(VObject * obj)
{

	register int i;
	register VObject *newObj;

	newObj = memory_allocate(sizeof(VObject), NULL);
	newObj->name = obj->name;
	newObj->extent = obj->extent;
	newObj->center = obj->center;
	newObj->numPolys = obj->numPolys;
	newObj->polygon = memory_allocate(sizeof(VPolygon *) * newObj->numPolys, NULL);
	if (obj->order) {
		newObj->order = memory_allocate(sizeof(unsigned short) * NUM_ASPECTS *
			newObj->numPolys, NULL);

		memcpy((char *) newObj->order, (char *) obj->order,
			   sizeof(unsigned short) * NUM_ASPECTS *
			   newObj->numPolys);
	}
	else {
		newObj->order = (unsigned short *) NULL;
	}

	for (i = 0; i < obj->numPolys; ++i) {
		if ((newObj->polygon[i] = VCopyPolygon(obj->polygon[i]))
			== (VPolygon *) NULL) {
			error_internal("VCopyObject: can't copy polygons", 0);
		}
	}

	return newObj;
}


int
VObjectNeedsOrdering(VObject * obj)
{
	VPolygon **p;
	int       i;

	if (obj->order || obj->numPolys == 0) {
		return 0;
	}

	for (i = 0, p = obj->polygon; i < obj->numPolys; ++i) {
#ifdef notdef
		if (p[i]->color != c) {
			return 1;
		}
#endif
		if (p[i]->backColor) {
			return 1;
		}
	}

	return 0;
}


void
VComputePolygonOrdering(VObject * obj)
{
	register int i, j, k, inserted;
	register double d, dn;
	VMatrix   mtx;
	VPoint    pt1, pt2;
	VPolygon **p;
	struct entry ent[VmaxVP], *head, *e, *last_e;

	obj->order = memory_allocate(sizeof(unsigned short) * NUM_ASPECTS * obj->numPolys, NULL);

	for (i = 0; i < NUM_ASPECTS; ++i) {

		VIdentMatrix(&mtx);
		VRotate(&mtx, YRotation, view[i].pitch);
		VRotate(&mtx, ZRotation, view[i].yaw);
		pt1.x = obj->extent * 10.0;
		pt1.y = pt1.z = 0.0;
		VTransform(&pt1, &mtx, &pt2);
		head = (struct entry *) NULL;
		for (j = 0, p = obj->polygon; j < obj->numPolys; ++j) {

/*
 *  Compute the distance to the farthest vertex on this polygon
 */

			dn = mag(p[j]->vertex[0].x - pt2.x,
					 p[j]->vertex[0].y - pt2.y,
					 p[j]->vertex[0].z - pt2.z);

			for (k = 1; k < p[j]->numVtces; ++k) {
				d = mag(
						   p[j]->vertex[k].x - pt2.x,
						   p[j]->vertex[k].y - pt2.y,
						   p[j]->vertex[k].z - pt2.z);
				if (d > dn) {
					dn = d;
				}
			}

			ent[j].id = j;
			ent[j].d = dn;

/*
 *  Insert this entry into the descending sorted list of polygons
 */

			if (!head) {
				ent[j].next = head;
				head = &ent[j];
			}
			else {
				last_e = (struct entry *) NULL;
				inserted = 0;
				for (e = head; e; e = e->next) {
					if (e->d < ent[j].d) {
						if (last_e) {
							ent[j].next = last_e->next;
							last_e->next = &ent[j];
						}
						else {
							ent[j].next = e;
							head = &ent[j];
						}
						inserted = 1;
						break;
					}
					last_e = e;
				}
				if (inserted == 0) {
					last_e->next = &ent[j];
					ent[j].next = (struct entry *) NULL;

				}
			}
		}

/*
 *  Copy this list as the hints for this quadrant.
 */

		k = obj->numPolys * i;
		for (j = 0, e = head; j < obj->numPolys; ++j, e = e->next) {
			obj->order[k + j] = e->id;
		}

	}
}


static VPoint scale =
{1.0, 1.0, 1.0};

void
VSetReadObjectScale(VPoint * p)
{
	scale = *p;
}

typedef enum {
	Nil,
	EndOfFile,
	TOKEN_RGB_VALUE,
	TOKEN_LPAREN,
	TOKEN_RPAREN,
	TOKEN_COMMA,
	TOKEN_STRING,
	TOKEN_CLIP,
	TOKEN_DOUBLE,
	TOKEN_LONG,
} field_id;


struct keyword_info {
	char     *word;
	field_id  id;
};

static struct keyword_info keywords[] =
{
	{"clip", TOKEN_CLIP},
	{(char *) NULL, Nil},
};

static char token[256];
static int token_length = 0;

typedef union {
	struct keyword_info *kw;
	double    double_value;
	char     *string_value;
	long      long_value;
	VPoint    point;
	VPolygon *poly;
} lex_val;

static lex_val lex_value;

struct lex_record {
	char     *name;
	FILE     *f;
	int       lineno;
	int       lookahead_valid;
	int       lookahead;
	int       stack_top;
	lex_val   value_stack[4];
};

#define push_value(p, type, val) \
	p->value_stack[p->stack_top++].type = val

#define pop_value(p, type) (p->value_stack[--p->stack_top].type)

#define input(p)	(p->lookahead_valid \
				? (p->lookahead_valid = 0, p->lookahead) \
				: (((p->lookahead = getc(p->f)) == '\n') \
					? (p->lineno++, p->lookahead) \
					: p->lookahead))

#define unput(p, c)	{ p->lookahead = c; p->lookahead_valid = 1; }

#define STATE_INITIAL	0
#define STATE_WORD	1
#define STATE_NUMBER	2
#define STATE_STRING	3
#define STATE_RGB	4


static void
ParseError(p, s)
struct lex_record *p;
char     *s;
{
	fprintf(stderr, "error in file %s (line %d):\n\t%s\n", p->name,
			p->lineno, s);
}


static field_id NextToken(struct lex_record *p)
{
	register int c, state = STATE_INITIAL, seen_dot = 0;
	register struct keyword_info *q;

	token_length = 0;

	while ((c = input(p)) != EOF) {

		switch (state) {

		case STATE_INITIAL:

			if (isalpha(c)) {
				token[token_length++] = c;
				state = STATE_WORD;
			}
			else if (isspace(c)) {
				continue;
			}
			else if (isdigit(c) || c == '-' || c == '+' || c == '.') {
				if (c == '.')
					seen_dot = 1;
				token[token_length++] = c;
				state = STATE_NUMBER;
			}
			else if (c == '"') {
				state = STATE_STRING;
			}
			else if (c == '#') {
				token[token_length++] = c;
				state = STATE_RGB;
			}
			else {
				token[0] = c;
				token[1] = '\0';
#ifdef DEBUG
				printf("other %s\n", token);
#endif
				switch (c) {
				case ',':
					return TOKEN_COMMA;
				case '(':
					return TOKEN_LPAREN;
				case ')':
					return TOKEN_RPAREN;
				default:
					ParseError(p, "invalid character");
					state = STATE_INITIAL;
				}
			}
			break;

		case STATE_WORD:
		case STATE_NUMBER:
			if (isspace(c) || c == ',' || c == '(' || c == ')') {
				token[token_length] = '\0';
				unput(p, c);
				if (state == STATE_WORD) {
					for (q = keywords; q->word; ++q) {
						if (strcmp(q->word, token) == 0) {
							lex_value.kw = q;
							return q->id;
						}
					}
					return TOKEN_STRING;
				}
				else {
					if (seen_dot) {
						lex_value.double_value = atof(token);
						return TOKEN_DOUBLE;
					}
					else {
						lex_value.long_value = atoi(token);
						return TOKEN_LONG;
					}
				}
			}
			else {
				if (c == '.' || c == 'e' || c == 'E') {
					seen_dot = 1;
				}
				token[token_length++] = c;
			}
			break;

		case STATE_STRING:

			switch (c) {

			case '"':
				token[token_length] = '\0';
				return TOKEN_STRING;

			case '\n':
				ParseError(p, "strings cannot span a line");
				unput(p, c);
				state = STATE_INITIAL;
				break;

			case '\\':

				switch (c = input(p)) {

				case EOF:
					ParseError(p, "Premature End-of-file");
					break;

				case 'n':
					token[token_length++] = '\n';
					break;

				case 't':
					token[token_length++] = '\t';
					break;

				default:
					token[token_length++] = c;
					break;
				}

			default:
				token[token_length++] = c;
				break;
			}
			break;

		case STATE_RGB:
			while (isxdigit(c)) {
				token[token_length++] = c;
				c = input(p);
			}
			unput(p, c);
			token[token_length] = '\0';
			state = STATE_INITIAL;
			return TOKEN_RGB_VALUE;
/*NOTREACHED */ break;

		}
	}

	return EndOfFile;
}


/*
 *  Skip to the specified token, if token is Nil, then skip to the end of the
 *  current line.
 */

static void
Resync(p, token)
struct lex_record *p;
field_id  token;
{
	field_id  t;
	int       c;

	if (token == Nil) {
		while ((c = input(p)) != EOF) {
			if (c == '\n')
				break;
		}
	}
	else {
		while ((t = NextToken(p)) != EndOfFile) {
			if (t == token)
				break;
		}
	}

}


/*
 *  Parse a polygon's color information
 *
 *  color_information:  foreground_color
 *              |       '(' foreground_color [ background_color ] [ 'clip' ]  ')'
 *              ;
 */

static int
ParseColorInfo(struct lex_record *p, int is_depth_cueing)
{
	field_id  t;
	static VPolygon template;
	int       count = 0, done = 0;

	template.color = template.backColor = (VColor_Type *) NULL;
	template.flags = 0;

	t = NextToken(p);

	if (t == TOKEN_RGB_VALUE || t == TOKEN_STRING) {
		template.color = VColor_getByName(token, is_depth_cueing);
	}
	else if (t == TOKEN_LPAREN) {
		template.flags = 0;

		while (!done) {

			t = NextToken(p);

			switch (t) {

			case TOKEN_RGB_VALUE:
			case TOKEN_STRING:
				if (count++ == 0) {
					template.color =
						VColor_getByName(token, is_depth_cueing);
				}
				else {
					template.backColor =
						VColor_getByName(token, is_depth_cueing);
				}
				break;

			case TOKEN_COMMA:
				break;

			case TOKEN_CLIP:
				template.flags |= PolyClipBackface;
				break;

			case TOKEN_RPAREN:
				done = 1;
				break;

			default:
				break;
			}
		}

	}
	else {
		Resync(p, Nil);
		return 1;
	}

	push_value(p, poly, &template);
	return 0;
}


static int
ParseVertex(p)
struct lex_record *p;
{
	field_id  t;
	VPoint    pt;

	if (NextToken(p) != TOKEN_LONG) {
	}

	t = NextToken(p);

	if (t == TOKEN_DOUBLE) {
		pt.x = lex_value.double_value;
	}
	else if (t == TOKEN_LONG) {
		pt.x = lex_value.long_value;
	}
	else {
		return 1;
	}

	t = NextToken(p);

	if (t == TOKEN_DOUBLE) {
		pt.y = lex_value.double_value;
	}
	else if (t == TOKEN_LONG) {
		pt.y = lex_value.long_value;
	}
	else {
		return 1;
	}

	t = NextToken(p);

	if (t == TOKEN_DOUBLE) {
		pt.z = lex_value.double_value;
	}
	else if (t == TOKEN_LONG) {
		pt.z = lex_value.long_value;
	}
	else {
		return 1;
	}

	push_value(p, point, pt);
	return 0;
}


/*
 *  Parse a polygon description.  tmp is the vector containing the points list
 *  (all points vertices used in all polgons in this object). pts is a
 *  scratch point list used to build this polygon;  it is pre-allocated
 *  by the caller.
 */

static int
ParsePolygon(struct lex_record *p, VPoint *tmp, VPoint *pts, int is_depth_cueing)
{
	int       num_points, i, id;
	VPolygon  template;

	if (ParseColorInfo(p, is_depth_cueing) != 0) {
		ParseError(p, "invalid color specification");
		return 1;
	}

	template = *(pop_value(p, poly));

	if (NextToken(p) != TOKEN_LONG) {
		ParseError(p, "invalid polygon vertex count");
		return 1;
	}

	num_points = lex_value.long_value;

	for (i = 0; i < num_points; ++i) {
		if (NextToken(p) != TOKEN_LONG) {
			ParseError(p, "invalid polygon vertex");
			return 1;
		}
		id = lex_value.long_value;
		pts[i] = tmp[id - 1];
	}

	push_value(p, poly, VCreatePolygonFromTemplate(num_points, pts,
												   &template));
	return 0;
}


VObject  *
VReadObject(f)
FILE     *f;
{
	return VReadDepthCueuedObject(f, 0);
}


VObject  *
VReadDepthCueuedObject(FILE *f, int is_depth_cueing)
{

	char     *name;
	int       num_points, num_polys, i;
	VPoint   *tmp_points, *tmp_points1;
	VPolygon **polygons;
	VObject  *object;
	struct lex_record lr, *p;
	char      line[256];

	p = &lr;
	p->f = f;
	p->lineno = 1;
	p->lookahead_valid = 0;
	p->stack_top = 0;

	fgets(line, sizeof(line), f);
	p->lineno = 2;
	p->name = line;

	name = memory_strdup(line);

	if (NextToken(p) != TOKEN_LONG) {
		return (VObject *) NULL;
	}

	num_points = lex_value.long_value;

	if (NextToken(p) != TOKEN_LONG) {
		return (VObject *) NULL;
	}

	num_polys = lex_value.long_value;

/*
 *  Allocate temporary storage for the polygon vertices.  tmp_points1 is
 *  a vector of points used to build each polygon structure.  Also, allocate
 *  storage for the object's polygon vector.
 */

	tmp_points = memory_allocate(num_points * 2 * sizeof(VPoint), NULL);
	tmp_points1 = &tmp_points[num_points];
	polygons = memory_allocate(num_polys * sizeof(VPolygon *), NULL);

/*
 *  Get the vertices
 */

	for (i = 0; i < num_points; ++i) {
		if (ParseVertex(p) != 0) {
			return (VObject *) NULL;
		}
		tmp_points[i] = pop_value(p, point);
		tmp_points[i].x *= scale.x;
		tmp_points[i].y *= scale.y;
		tmp_points[i].z *= scale.z;
	}

/*
 *  Now get the polygon descriptions
 */

	for (i = 0; i < num_polys; ++i) {
		if (ParsePolygon(p, tmp_points, tmp_points1, is_depth_cueing) != 0) {
			ParseError(p, "invalid polygon specification");
			return (VObject *) NULL;
		}
		polygons[i] = pop_value(p, poly);
	}

/*
 *  Build the object structure
 */

	object = memory_allocate(sizeof(VObject), NULL);
	object->name = name;
	object->numPolys = num_polys;
	object->polygon = polygons;
	object->order = (unsigned short *) NULL;
	VComputeObjectExtent(object);

	if (VObjectNeedsOrdering(object)) {
		VComputePolygonOrdering(object);
	}

	memory_dispose((char *) tmp_points);
	return object;
}


int
VWriteObject(FILE * f, VObject * obj)
{

	int       i, j, k, points;
	VPolygon **q;
	VPoint   *p;

/*
 *  Total the number of vertices in all of the object's polygons
 */

	points = 0;
	q = obj->polygon;
	for (i = 0; i < obj->numPolys; ++i) {
		points += q[i]->numVtces;
	}

/*
 *  Print the header
 */

	fprintf(f, "%s\n%d %d\n", obj->name, points, obj->numPolys);

/*
 *  Print the point list
 */

	k = 1;
	q = obj->polygon;
	for (i = 0; i < obj->numPolys; ++i) {
		for ((j = 0, p = q[i]->vertex); j < q[i]->numVtces; (++p, ++j)) {
			fprintf(f, "%d %g %g %g\n", k, p->x, p->y, p->z);
			++k;
		}
	}

/*
 *  Print the polygon list
 */

	k = 1;
	q = obj->polygon;
	for (i = 0; i < obj->numPolys; ++i) {
		if (q[i]->backColor) {
			fprintf(f, "(%s",
					VColor_getName(q[i]->color));
			fprintf(f, " %s) %d",
					VColor_getName(q[i]->backColor),
					q[i]->numVtces);
		}
		else if (q[i]->flags & PolyClipBackface) {
			fprintf(f, "(%s clip) %d",
					VColor_getName(q[i]->color),
					q[i]->numVtces);
		}
		else {
			fprintf(f, "%s %d",
					VColor_getName(q[i]->color),
					q[i]->numVtces);
		}
		for (j = 0; j < q[i]->numVtces; ++j)
			fprintf(f, " %d", k++);
		fprintf(f, "\n");
	}

	return ferror(f) ? -1 : 0;
}


void
VComputeObjectExtent(VObject * obj)
{
	VPoint    sum;
	register int i, j, npts = 0;
	register double d;

	obj->extent = 0.0;
	sum.x = 0.0;
	sum.y = 0.0;
	sum.z = 0.0;

/*
 *  Add the xyz components of each point in the object so that we can
 *  determine the average location (i.e. the center).
 */

	for (i = 0; i < obj->numPolys; ++i) {

		for (j = 0; j < obj->polygon[i]->numVtces; ++j) {

			sum.x += obj->polygon[i]->vertex[j].x;
			sum.y += obj->polygon[i]->vertex[j].y;
			sum.z += obj->polygon[i]->vertex[j].z;
			++npts;
		}
	}

	if (npts != 0) {

		obj->center.x = sum.x / npts;
		obj->center.y = sum.y / npts;
		obj->center.z = sum.z / npts;

/*
 *   Determine the most distant point from the center of the object
 */

		for (i = 0; i < obj->numPolys; ++i) {
			for (j = 0; j < obj->polygon[i]->numVtces; ++j) {
				sum.x = obj->polygon[i]->vertex[j].x -
					obj->center.x;
				sum.y = obj->polygon[i]->vertex[j].y -
					obj->center.y;
				sum.z = obj->polygon[i]->vertex[j].z -
					obj->center.z;
				d = sqrt(sum.x * sum.x + sum.y * sum.y +
						 sum.z * sum.z);
				if (d > obj->extent)
					obj->extent = d;
			}
		}

	}
	else {
		obj->center.x = obj->center.y = obj->center.z = 0.0;
	}

}


#define STATE_NORMAL		0
#define STATE_ENTITIES		1
#define STATE_POLYLINE		2
#define STATE_3DFACE		3
#define STATE_VERTEX		4
#define STATE_BLOCK		5
#define STATE_INSERT		6
#define STATE_TABLES		7
#define STATE_GENERAL_ENTITY	8


static char *colors[] =
{
	"black",
	"red",
	"yellow",
	"green",
	"cyan",
	"blue",
	"magenta",
	"white"
};

#define POLY_MAX	(10 * 1024)
#define BOBJECT_MAX	128
#define POINT_MAX	32768

static VObject *bobject[BOBJECT_MAX];
static int btop = 0;

#ifdef DEBUG
#define PDEBUG(a)	printf a
#else
#define PDEBUG(a)
#endif

static int lineno = 0;

#define COMPARE(a,b)	strcmp(a,b)

enum _token_id {
		DXF_NULL,
		DXF_EOF,
		DXF_X_COORD,
		DXF_Y_COORD,
		DXF_Z_COORD,
		DXF_X_SCALE,
		DXF_Y_SCALE,
		DXF_Z_SCALE,
		DXF_ROTATE,
		DXF_SECTION,
		DXF_ENDSEC,
		DXF_SEQEND,
		DXF_TITLE,
		DXF_POLYLINE,
		DXF_3DFACE,
		DXF_ENTITIES,
		DXF_VERTEX,
		DXF_BLOCKS,
		DXF_INSERT,
		DXF_X_EXTRUDE,
		DXF_Y_EXTRUDE,
		DXF_Z_EXTRUDE,
		DXF_M_COUNT,
		DXF_N_COUNT,
		DXF_THIRD_VERTEX,
		DXF_FOURTH_VERTEX,
		DXF_FLAGS,
		DXF_ITEM,
		DXF_TABLES,
		DXF_TABLE,
		DXF_ENDTAB,
		DXF_LAYER,
		DXF_STYLE,
		DXF_LTYPE,
		DXF_ATTDEF,
		DXF_ATTRIB,
		DXF_DICTIONARY,	/* found in OBJECTS section */
		DXF_MLINESTYLE,
		DXF_ACAD_GROUP,
		DXF_ACAD_MLINESTYLE,
		DXF_APPID,		/* found in TABLES and BLOCKS section */
		DXF_BLOCK,
		DXF_ENDBLK,
		DXF_VPORT,
		DXF_VIEW,
		DXF_COLOR_INDEX
};

typedef enum _token_id dxf_token_id;

typedef struct {
    dxf_token_id id;
    char *name;
} token_table;

token_table a[] =
{
    {DXF_SECTION,    "SECTION"},
    {DXF_ENDSEC,     "ENDSEC"},
    {DXF_POLYLINE,   "POLYLINE"},
    {DXF_3DFACE,     "3DFACE"},
    {DXF_VERTEX,     "VERTEX"},
    {DXF_SEQEND,     "SEQEND"},
    {DXF_EOF,        "EOF"},
    {DXF_BLOCK,      "BLOCK"},
    {DXF_ENDBLK,     "ENDBLK"},
    {DXF_INSERT,     "INSERT"},
    {DXF_TABLE,      "TABLE"},
    {DXF_ENDTAB,     "ENDTAB"},
    {DXF_LAYER,      "LAYER"},
    {DXF_STYLE,      "STYLE"},
    {DXF_LTYPE,      "LTYPE"},
    {DXF_VPORT,      "VPORT"},
    {DXF_DICTIONARY, "DICTIONARY"},
    {DXF_MLINESTYLE, "MLINESTYLE"},
    {DXF_APPID,      "APPID"},
    {DXF_ACAD_GROUP, "ACAD_GROUP"},
    {DXF_ACAD_MLINESTYLE, "ACAD_MLINESTYLE"},
    {DXF_ATTDEF,    "ATTDEF"},
    {DXF_ATTRIB,     "ATTRIB"},
    {DXF_EOF, NULL}
};

static int int_value;


static dxf_token_id
ReadToken(FILE *f, double *fp_value, char *cp_value, int *code, char *string)
{
    long i;
    int len;
    token_table *p;
    char buf1[512], buf2[512], *r1, *r2;
	
    r1 = fgets(buf1, sizeof(buf1), f);
    r2 = fgets(buf2, sizeof(buf2), f);
    lineno += 2;
	
	/*
	 *  Remove the trailing newline ...
	 */
	
    len = strlen(buf1);
    if (len > 0) {
		buf1[len - 1] = '\0';
    }
	
	/*
	 *  File in MSDOS format?
	 */
	
    if (len >= 2 && buf1[len - 2] == '\r') {
		buf1[len - 2] = '\0';
    }
	
	/*
	 *  Remove the trailing newline ...
	 */
	
    len = strlen(buf2);
    if (len > 0) {
		buf2[len - 1] = '\0';
    }
	
	/*
	 *  File in MSDOS format?
	 */
	
    if (len >= 2 && buf2[len - 2] == '\r') {
		buf2[len - 2] = '\0';
    }
	
    strcpy(cp_value, buf2);
    strcpy(string, buf2);
	
    if (r1 != (char *) NULL && r2 != (char *) NULL) {
		i = strtol(buf1, (char **) NULL, 0);
		*code = i;
		if (i == 0) {
			for (p = a; p->name != (char *) NULL; ++p) {
				if (COMPARE(p->name, buf2) == 0) {
					/*	    PDEBUG(("token: %s  --> ", p->name)); */
					return p->id;
				}
			}
			printf ("Warning: unrecognized directive, \"%s\"\n",
				buf2);
		}
		else if (i == 2) {
			if (COMPARE("ENTITIES", buf2) == 0) {
				return DXF_ENTITIES;
			}
			else if (COMPARE("BLOCKS", buf2) == 0) {
				return DXF_BLOCKS;
			}
			else if (COMPARE("TABLES", buf2) == 0) {
				return DXF_TABLES;
			}
			else {
				return DXF_TITLE;
			}
		}
		else if (i >= 10 && i <= 19) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_X_COORD;
		}
		else if (i >= 20 && i <= 29) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_Y_COORD;
		}
		else if (i >= 30 && i <= 39) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_Z_COORD;
		}
		else if (i == 41) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_X_SCALE;
		}
		else if (i == 42) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_Y_SCALE;
		}
		else if (i == 43) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_Z_SCALE;
		}
		else if (i == 50) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_ROTATE;
		}
		else if (i == 62) {
			int_value = strtol(buf2, (char **) NULL, 0);
			return DXF_COLOR_INDEX;
		}
		else if (i == 70) {
			int_value = strtol(buf2, (char **) NULL, 0);
			return DXF_FLAGS;
		}
		else if (i == 71) {
			int_value = strtol(buf2, (char **) NULL, 0);
			return DXF_M_COUNT;
		}
		else if (i == 72) {
			int_value = strtol(buf2, (char **) NULL, 0);
			return DXF_N_COUNT;
		}
		else if (i == 73) {
			int_value = strtol(buf2, (char **) NULL, 0);
			return DXF_THIRD_VERTEX;
		}
		else if (i == 74) {
			int_value = strtol(buf2, (char **) NULL, 0);
			return DXF_FOURTH_VERTEX;
		}
		else if (i == 210) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_X_EXTRUDE;
		}
		else if (i == 220) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_Y_EXTRUDE;
		}
		else if (i == 230) {
			*fp_value = strtod(buf2, (char **) NULL);
			return DXF_Z_EXTRUDE;
		}
		else {
			return DXF_ITEM;
		}
    }
    return DXF_EOF;
}


static int lookahead_valid = 0;
static double fp_la;
static dxf_token_id token_id_la;
static char cp_la[256], string_la[256];
static int code_la;


static dxf_token_id
DXFNextToken(FILE * f, double *fp_value, char *cp_value, int * code, char *string)
{
    if (lookahead_valid) {
		lookahead_valid = 0;
		*fp_value = fp_la;
		strcpy(cp_value, cp_la);
		strcpy(string, string_la);
		*code = code_la;
		return token_id_la;
	}
    else {
		return ReadToken(f, fp_value, cp_value, code, string);
    }
}


static void
PushToken(dxf_token_id id, double fp_value, char *cp_value, int code, char *string)
{
    lookahead_valid = 1;
    fp_la = fp_value;
    token_id_la = id;
    strcpy(cp_la, cp_value);
    strcpy(string_la, string);
    code_la = code;
}

static void
InsertBlock(char *name, VPoint * o, VPoint * scale, VPoint * extrude, double r,
			VPolygon **poly, int *ptop)
{
    VObject **p;
    int i, j, extrusion = 0;
	
    PDEBUG(("looking for \"%s\"; offset %g, %g, %g; scale %g, %g, %g; extrude %g, %g, %g; rotate %g\n", name,
		o->x, o->y, o->z, scale->x, scale->y, scale->z,
		extrude->x, extrude->y, extrude->z, r));
	
    for (p = bobject, i = 0; i < btop; ++i, ++p) {
		if (strcmp((*p)->name, name) == 0) {
			
		/*
		 *  If this is an extrusion, create a temporary object representing the
		 *  given extrusion direction.
		 */
			
#ifdef notdef
			if (extrude->x != 0.0 || extrude->y != 0.0 || extrude->z != 0.0) {
				PDEBUG(("extrusion (%f, %f, %f)\n", extrude->x,
					extrude->y, extrude->z));
				extrusion = 1;
				ex_tmp = VExtrudeObject(*p, extrude);
				p = &ex_tmp;
			}
#endif
			
			/*
			 *  Add points to the block ...
			 */
			
			PDEBUG(("adding %d polygons to %d existing\n",
				(*p)->numPolys, (*ptop)));
			for (j = 0; j < (*p)->numPolys; ++j) {
				poly[(*ptop)++] = ScalePolygon((*p)->polygon[j], o, scale, extrude, r);
			}
			
			/*
			 *  Delete the temporarily created extrusion object
			 */
			
			if (extrusion) {
				for (j = 0; j < (*p)->numPolys; ++j) {
					VDestroyPolygon((*p)->polygon[j]);
					memory_dispose((char *) (*p));
				}
			}
			
			return;
		}
    }
}


VObject  *
VReadDXFObject(FILE *f)
{
	return VReadDepthCueuedObject(f, 0);
}


VObject  *
VReadDepthCueuedDXFObject(FILE *f, int flag)
{
	
    double value, rotate = 0;
    dxf_token_id id;
    int i, j, m0 = 0, n0 = 0, vertices_listed_by_index = 0;
    VPoint temp[POINT_MAX], temp1[4], *p, scale, pt, bpt, extrude;
    VPolygon template;
    VObject *object;
    int top = 0;
    int state = STATE_NORMAL;
    char cp[256], title[256], insert_title[256], *stop_block = "<none>";
    int order = 0;
    int indices[4], num_indices = 0;
    VMatrix m;
    int code;
    char string[256];

	VColor_Type *blackColor = VColor_getByName("black", 0);

	VPolygon *poly[POLY_MAX];
	int ptop = 0;
	VSetPoint(&bpt, 0.0, 0.0, 0.0);
	
	/*
	 *  Start with the first of our selected colors.  If ordering was reqested,
	 *  make the same color the "backface color".
	 */
	
	memset(&template, 0, sizeof(VPolygon));

    template.color = blackColor;
    template.backColor = NULL;
    if (order) {
        template.backColor = template.color;
    }
	
	/*
	 *  I'm not sure about backface clipping, yet.
	 */
    template.flags = 0;
	/*  template.flags = PolyClipBackface; */
	
    p = &temp[top];
	
    while (1) {
		id = DXFNextToken(f, &value, cp, &code, string);
		switch (state) {
		case STATE_NORMAL:
			///    PDEBUG(("NORMAL: %s\n", string));
			switch (id) {
			case DXF_ENTITIES:
				state = STATE_ENTITIES;
				break;
			case DXF_BLOCKS:
				state = STATE_ENTITIES;
				break;
			case DXF_TABLES:
				state = STATE_TABLES;
				break;
			case DXF_EOF:
				
			/*
			 *  Build the object structure
		     */
				
				object = memory_allocate(sizeof(VObject), NULL);
				memset (object, 0, sizeof(VObject));
				object->name = memory_strdup("name");
				object->numPolys = ptop;
				object->polygon = memory_allocate(ptop * sizeof(VPolygon *), NULL);
				memcpy(object->polygon, poly, ptop * sizeof(VPolygon *));
				object->order = (unsigned short *) NULL;
				VComputeObjectExtent(object);

				if (VObjectNeedsOrdering(object)) {
					VComputePolygonOrdering(object);
				}

				/*
				 *  Change to V library axes (Z-axis = down);
				 */
				
				VIdentMatrix(&m);
#ifdef notdef
				m.m[2][1] = 1.0;
				m.m[2][2] = 0.0;
				m.m[1][1] = 0.0;
				m.m[1][2] = -1.0;
#endif
				m.m[2][2] = -1.0;
				m.m[1][1] = -1.0;
				
				for (i = 0; i < object->numPolys; ++i) {
					for (j = 0; j < object->polygon[i]->numVtces; ++j) {
						VTransform_(&object->polygon[i]->vertex[j], &m, &pt);
						object->polygon[i]->vertex[j] = pt;
					}
				}
				
				return object;
				
			default:
				break;
			}
			break;
			
			/*
			 *  Skip table definitions (for now) ...
			 */
			
			case STATE_TABLES:
				switch (id) {
				case DXF_ENDSEC:
					state = STATE_NORMAL;
					break;
				default:
					break;
				}
				break;
				
				case STATE_ENTITIES:
					if (code == 0) {
						// close-out last entity
#ifdef notdef
						printf ("Entity closed 0, \"%s\"\n", string);
#endif
					}
					// PDEBUG(("ENTITIES: %s\n", string));
					switch (id) {
					case DXF_ATTDEF:
					case DXF_ATTRIB:
						state = STATE_GENERAL_ENTITY;
						break;
					case DXF_POLYLINE:
						state = STATE_POLYLINE;
						p = &temp[0];
						top = 0;
						m0 = n0 = 0;
						vertices_listed_by_index = num_indices = 0;
						break;
					case DXF_3DFACE:
						state = STATE_3DFACE;
						p = &temp[0];
						top = 0;
						break;
					case DXF_BLOCK:
						state = STATE_BLOCK;
						strcpy (title, "*none*");
						bpt.x = bpt.y = bpt.z = 0.0;
						break;
					case DXF_ENDBLK:
						state = STATE_BLOCK;
						PushToken(id, value, cp, code, string);
						break;
					case DXF_INSERT:
						pt.x = pt.y = pt.z = 0.0;
						scale.x = 1.0;
						scale.y = 1.0;
						scale.z = 1.0;
						rotate = 0.0;
						extrude = pt;
						extrude.z = 1.0;
						state = STATE_INSERT;
						strcpy (insert_title, "*not-specified*");
						break;
					case DXF_ENDSEC:
						state = STATE_NORMAL;
						break;
					default:
						if (code != 0) {
							printf ("hmm, %d \"%s\"\n", code, string);
						}
						else {
							state = STATE_GENERAL_ENTITY;
						}
						break;
					case DXF_EOF:
						break;
					}
					break;
					
					case STATE_POLYLINE:
						//PDEBUG(("POLYLINE: %s\n", string));
						switch (id) {
						case DXF_COLOR_INDEX:
							if (int_value < 8) {
								error_internal("variable `i' uninitialized", 0);
								/* FIXME
								template.color = VAllocColor (colors[i]);
								*/
								if (order) {
									template.backColor = template.color;
								}
							}
							else {
								printf ("color index %d\n", int_value);
							}
							break;

						case DXF_SEQEND:
							
						/*
						 * Mesh?
						 */
							
							if (vertices_listed_by_index == 0) {
								if (m0 != 0 || n0 != 0) {
									for (i = 1; i < m0; ++i) {
										for (j = 1; j < n0; ++j) {
											temp1[0] = temp[(i - 1) * n0 + j - 1];
											temp1[1] = temp[(i) * n0 + j - 1];
											temp1[2] = temp[(i) * n0 + j];
											poly[ptop++] = VCreatePolygonFromTemplate(3, temp1,
												&template);
											
											temp1[0] = temp[(i) * n0 + j];
											temp1[1] = temp[(i - 1) * n0 + j];
											temp1[2] = temp[(i - 1) * n0 + j - 1];
											poly[ptop++] = VCreatePolygonFromTemplate(3, temp1,
												&template);
										}
									}
									PDEBUG(("mesh %d x %d\n", m0, n0));
								}
								else {
									poly[ptop++] = VCreatePolygonFromTemplate(top, temp,
										&template);
								}
							}
							PDEBUG(("added POLYLINE polygon number %d\n", ptop - 1));
#ifdef notdef
							VPrintPolygon(stdout, poly[ptop - 1]);
#endif
							p = &temp[0];
							top = 0;
							state = STATE_ENTITIES;
							break;
						case DXF_FLAGS:
							if ((int_value & 2) || (int_value & 4)) {
								printf ("Warning: POLYLINE splines or curves not supported by Vlib (line %d).\n", lineno);
							}
							break;
						case DXF_VERTEX:
							state = STATE_VERTEX;
							break;
						case DXF_M_COUNT:
							m0 = int_value;
							break;
						case DXF_N_COUNT:
							n0 = int_value;
							break;
						default:
							break;
						}
						break;
						
						case STATE_3DFACE:
							// PDEBUG(("3DFACE: %s\n", string));
							switch (id) {
							case DXF_COLOR_INDEX:
								if (int_value < 8) {
									template.color = VColor_getByName(colors[int_value], 0);
									if (order) {
										template.backColor = template.color;
									}
								}
								else {
									printf ("color index %d\n", int_value);
								}
								break;

							case DXF_ENDBLK:
								state = STATE_BLOCK;
								PushToken(id, value, cp, code, string);
								/* create polygon */
								poly[ptop++] = VCreatePolygonFromTemplate(top, temp,
									&template);
								p = &temp[0];
								top = 0;
								break;
								
							case DXF_X_COORD:
								p->x = value;
								if ((id = DXFNextToken(f, &p->y, cp, &code, string)) != DXF_Y_COORD) {
									printf("syntax error4 %d\n", id);
								}
								
								if ((id = DXFNextToken(f, &p->z, cp, &code, string)) != DXF_Z_COORD) {
									printf("syntax error5 %d\n", id);
								}
								
								++p;
								++top;
								if (top == POINT_MAX) {
									printf ("Point overflow, increase POINT_MAX.\n");
								}
								break;
								
							case DXF_3DFACE:
							case DXF_POLYLINE:
							case DXF_ENDSEC:
							default:
								if (code == 0) {
								/*
								*  Turbocad bug
									*/
									if (temp[top-1].x == temp[top-2].x &&
										temp[top-1].y == temp[top-2].y &&
										temp[top-1].z == temp[top-2].z) {
										-- top;
									}
									/* create polygon */
									poly[ptop++] = VCreatePolygonFromTemplate(top, temp,
										&template);
#ifdef notdef
									printf("polygon %d\n", ptop - 1);
									VPrintPolygon(stdout, poly[ptop - 1]);
#endif
									p = &temp[0];
									top = 0;
									PushToken(id, value, cp, code, string);
									state = STATE_ENTITIES;
								}
								
								break;
							}
							break;
							
							/*
							*  Get X,Y,Z components following a VERTEX directive
							*/
							
							case STATE_VERTEX:
								// PDEBUG(("VERTEX: %d\n", id));
								switch (id) {
								case DXF_COLOR_INDEX:
								if (int_value < 8) {
									error_internal("var `i' uninitialized", 0);
									/* FIXME
									template.color = VAllocColor (colors[i]);
									*/
									if (order) {
										template.backColor = template.color;
									}
								}
								else {
									printf ("color index %d\n", int_value);
								}
								break;

								case DXF_X_COORD:
									p->x = value;
									break;
								case DXF_Y_COORD:
									p->y = value;
									break;
								case DXF_Z_COORD:
									p->z = value;
									break;
									
									/*
									*  M_COUNT and N_COUNT really are the first and second vertex indices in
									*  a VERTEX.
									*/
								case DXF_M_COUNT:
									vertices_listed_by_index = 1;
									if (int_value < 0) {
										int_value = - int_value;
									}
									indices[0] = int_value - 1;
									num_indices = 1;
									break;
								case DXF_N_COUNT:
									vertices_listed_by_index = 1;
									if (int_value < 0) {
										int_value = - int_value;
									}
									indices[1] = int_value - 1;
									num_indices = 2;
									break;
								case DXF_THIRD_VERTEX:
									vertices_listed_by_index = 1;
									if (int_value < 0) {
										int_value = - int_value;
									}
									indices[2] = int_value - 1;
									num_indices = 3;
									break;
								case DXF_FOURTH_VERTEX:
									vertices_listed_by_index = 1;
									if (int_value < 0) {
										int_value = - int_value;
									}
									indices[3] = int_value - 1;
									num_indices = 4;
									break;
								case DXF_SEQEND:
								case DXF_VERTEX:
									if (vertices_listed_by_index) {
										for (i=0; i<num_indices; ++i) {
											if (indices[i] >= top) {
												printf ("internal error polygon vertex out of range: %d (max %d) -- %d\n",
													indices[i], top, i);
											}
											temp1[i] = temp[indices[i]];
										}
										poly[ptop++] = VCreatePolygonFromTemplate(num_indices, temp1,
											&template);
									}
									else {
										++p;
										++top;
										if (top == POINT_MAX) {
											printf ("Point overflow, increase POINT_MAX.\n");
										}
									}
									PushToken(id, value, cp, code, string);
									state = STATE_POLYLINE;
									break;
								default:
									break;
								}
								
								break;
								
								case STATE_INSERT:
									// PDEBUG(("INSERT: %s\n", string));
									switch (id) {
									case DXF_ENDBLK:
										state = STATE_BLOCK;
										PushToken(id, value, cp, code, string);
										
										InsertBlock(insert_title, &pt, &scale,
											&extrude, rotate, poly, &ptop);
										
										break;
									case DXF_TITLE:
										strcpy(insert_title, cp);
										break;
									case DXF_X_COORD:
										pt.x = value;
										break;
									case DXF_Y_COORD:
										pt.y = value;
										break;
									case DXF_Z_COORD:
										pt.z = value;
										break;
									case DXF_X_SCALE:
										scale.x = value;
										break;
									case DXF_Y_SCALE:
										scale.y = value;
										break;
									case DXF_Z_SCALE:
										scale.z = value;
										break;
									case DXF_ROTATE:
										rotate = value;
										break;
									case DXF_X_EXTRUDE:
										extrude.x = value;
										break;
									case DXF_Y_EXTRUDE:
										extrude.y = value;
										break;
									case DXF_Z_EXTRUDE:
										extrude.z = value;
										break;
									case DXF_INSERT:
									case DXF_POLYLINE:
									case DXF_3DFACE:
									case DXF_ENDSEC:
									default:
										if (code == 0) {
											state = STATE_ENTITIES;
											PushToken(id, value, cp, code, string);
											
											InsertBlock(insert_title, &pt, &scale,
												&extrude, rotate, poly, &ptop);
										}
										break;
									}
									break;
									
									case STATE_GENERAL_ENTITY:
										if (code == 0) {
											state = STATE_ENTITIES;
											PushToken(id, value, cp, code, string);
										}
										break;
										
									case STATE_BLOCK:
										PDEBUG(("BLOCK: %s\n", string));
										switch (id) {
										case DXF_ENDBLK:
										/*
										 *  Build the object structure and place it on the block list
									     */
											object = memory_allocate(sizeof(VObject), NULL);
											object->name = memory_allocate(strlen(title) + 1, NULL);
											strcpy(object->name, title);
											PDEBUG(("added block \"%s\" %d polygons (line %d)\n", title, ptop, lineno));
											object->numPolys = ptop;
											object->polygon = memory_allocate(ptop * sizeof(VPolygon *), NULL);
											memcpy((char *) object->polygon,
												(char *) poly, ptop * sizeof(VPolygon *));
											ptop = 0;
											object->order = NULL;
											PDEBUG(("Block offset %f, %f, %f\n",
												bpt.x, bpt.y, bpt.z));
											
											for (i = 0; i < object->numPolys; ++i) {
												for (j = 0; j < object->polygon[i]->numVtces; ++j) {
													object->polygon[i]->vertex[j].x += bpt.x;
													object->polygon[i]->vertex[j].y += bpt.y;
													object->polygon[i]->vertex[j].z += bpt.z;
												}
											}
											
											bobject[btop++] = object;
											
											state = STATE_ENTITIES;
											if (strcmp(stop_block, object->name) == 0) {
												VIdentMatrix(&m);
#ifdef notdef
												m.m[2][1] = 1.0;
												m.m[2][2] = 0.0;
												m.m[1][1] = 0.0;
												m.m[1][2] = -1.0;
#endif
												m.m[2][2] = -1.0;
												m.m[1][1] = -1.0;
												
												for (i = 0; i < object->numPolys; ++i) {
													for (j = 0; j < object->polygon[i]->numVtces; ++j) {
														VTransform(&object->polygon[i]->vertex[j], &m, &pt);
														object->polygon[i]->vertex[j] = pt;
													}
												}
												
												return object;
											}
											
											break;
										case DXF_TITLE:
											strcpy(title, cp);
											break;
										case DXF_X_COORD:
											bpt.x = value;
											break;
										case DXF_Y_COORD:
											bpt.y = value;
											break;
										case DXF_Z_COORD:
											bpt.z = value;
											break;
										case DXF_POLYLINE:
										case DXF_3DFACE:
										case DXF_INSERT:
										default:
											if (code == 0) {
												state = STATE_ENTITIES;
												PushToken(id, value, cp, code, string);
											}
											break;
										}
	}
    }
	return 0;
}


#ifdef FIXME_NOT_USED

static void
ArbitraryAxis (VPoint *normal, VMatrix *out)
{
	double minval = 1.0 / 64.0, d;
	VPoint Ax, Ay, Az;

	d = sqrt (normal->x * normal->x + normal->y * normal->y + normal->z * normal->z);
	Az.x = normal->x / d;
	Az.y = normal->y / d;
	Az.z = normal->z / d;

	if (fabs (Az.x) < minval && fabs (Az.y) < minval) {
		VCrossProd(&VUnitVectorJ, &Az, &Ax);
	}
	else {
		VCrossProd(&VUnitVectorK, &Az, &Ax);
	}

	VCrossProd (&Az, &Ax, &Ay);

	out->m[0][0] = Ax.x;
	out->m[0][1] = Ax.y;
	out->m[0][2] = Ax.z;

	out->m[1][0] = Ay.x;
	out->m[1][1] = Ay.y;
	out->m[1][2] = Ay.z;

	out->m[2][0] = Az.x;
	out->m[2][1] = Az.y;
	out->m[2][2] = Az.z;

	out->m[3][0] = out->m[3][1] = out->m[3][2] = 0.0;
	out->m[0][3] = out->m[1][3] = out->m[2][3] = 0.0;
	out->m[3][3] = 1.0;
}

#endif /* FIXME_NOT_USED */


/* The VObjects.c module ends here */
