#ifndef lint
static const char RCSid[] = "$Id: writewfobj.c,v 2.3 2020/04/23 22:35:27 greg Exp $";
#endif
/*
 *  writewfobj.c
 *
 *  Routines for writing out Wavefront file.
 *
 *  Created by Greg Ward on Thu Feb 12 2004.
 */

#include "paths.h"
#include "rterror.h"
#include "objutil.h"

/* Delete unreferenced vertices */
static void
del_unref_verts(Scene *sc)
{
	int     *vmap;
	Face    *f;
	int     nused, i;
						/* allocate index map */
	if (!sc->nverts)
		return;
	i = sc->nverts;
	if (sc->ntex > i)
		i = sc->ntex;
	if (sc->nnorms > i)
		i = sc->nnorms;
	vmap = (int *)emalloc(sizeof(int)*i);
						/* remap positions */
	for (i = nused = 0; i < sc->nverts; i++) {
		if (sc->vert[i].vflist == NULL) {
			vmap[i] = -1;
			continue;
		}
		if (nused != i)
			sc->vert[nused] = sc->vert[i];
		vmap[i] = nused++;
	}
	if (nused == sc->nverts)
		goto skip_pos;
	sc->vert = (Vertex *)erealloc((char *)sc->vert,
				sizeof(Vertex)*(nused+(CHUNKSIZ-1)));
	sc->nverts = nused;
	for (f = sc->flist; f != NULL; f = f->next)
		for (i = f->nv; i--; )
			if ((f->v[i].vid = vmap[f->v[i].vid]) < 0)
				error(CONSISTENCY,
					"Link error in del_unref_verts()");
skip_pos:
						/* remap texture coord's */
	if (!sc->ntex)
		goto skip_tex;
	memset((void *)vmap, 0, sizeof(int)*sc->ntex);
	for (f = sc->flist; f != NULL; f = f->next)
		for (i = f->nv; i--; )
			if (f->v[i].tid >= 0)
				vmap[f->v[i].tid] = 1;
	for (i = nused = 0; i < sc->ntex; i++) {
		if (!vmap[i])
			continue;
		if (nused != i)
			sc->tex[nused] = sc->tex[i];
		vmap[i] = nused++;
	}
	if (nused == sc->ntex)
		goto skip_tex;
	sc->tex = (TexCoord *)erealloc((char *)sc->tex,
				sizeof(TexCoord)*(nused+(CHUNKSIZ-1)));
	sc->ntex = nused;
	for (f = sc->flist; f != NULL; f = f->next)
		for (i = f->nv; i--; )
			if (f->v[i].tid >= 0)
				f->v[i].tid = vmap[f->v[i].tid];
skip_tex:
						/* remap normals */
	if (!sc->nnorms)
		goto skip_norms;
	memset((void *)vmap, 0, sizeof(int)*sc->nnorms);
	for (f = sc->flist; f != NULL; f = f->next)
		for (i = f->nv; i--; )
			if (f->v[i].nid >= 0)
				vmap[f->v[i].nid] = 1;
	for (i = nused = 0; i < sc->nnorms; i++) {
		if (!vmap[i])
			continue;
		if (nused != i)
			memcpy((void *)sc->norm[nused],
					(void *)sc->norm[i],
					sizeof(Normal));
		vmap[i] = nused++;
	}
	if (nused == sc->nnorms)
		goto skip_norms;
	sc->norm = (Normal *)erealloc((char *)sc->norm,
				sizeof(Normal)*(nused+(CHUNKSIZ-1)));
	sc->nnorms = nused;
	for (f = sc->flist; f != NULL; f = f->next)
		for (i = f->nv; i--; )
			if (f->v[i].nid >= 0)
				f->v[i].nid = vmap[f->v[i].nid];
skip_norms:
						/* clean up */
	efree((char *)vmap);
}

/* Write out header comments */
static void
write_header(Scene *sc, FILE *fp)
{
	int     i;
	
	for (i = 0; i < sc->ndescr; i++)
		fprintf(fp, "# %s\n", sc->descr[i]);
	fputs("#\n", fp);
	fprintf(fp, "# %d final faces\n", sc->nfaces);
	fprintf(fp,
		"#\t%d vertices, %d texture coordinates, %d surface normals\n",
			sc->nverts, sc->ntex, sc->nnorms);
	fputs("#\n\n", fp);
}

/* Write out vertex lists */
static void
write_verts(Scene *sc, FILE *fp)
{
	int     i;
	
	fputs("# Vertex positions\n", fp);
	for (i = 0; i < sc->nverts; i++)
		fprintf(fp, "v %.12g %.12g %.12g\n",
				sc->vert[i].p[0],
				sc->vert[i].p[1],
				sc->vert[i].p[2]);
	fputs("\n# Vertex texture coordinates\n", fp);
	for (i = 0; i < sc->ntex; i++)
		fprintf(fp, "vt %.6g %.6g\n",
				sc->tex[i].u, sc->tex[i].v);
	fputs("\n# Vertex normals\n", fp);
	for (i = 0; i < sc->nnorms; i++)
		fprintf(fp, "vn %.6f %.6f %.6f\n",
				sc->norm[i][0],
				sc->norm[i][1],
				sc->norm[i][2]);
	fputc('\n', fp);
}

/* Write out an object group */
static void
write_group(const Scene *sc, int gid, FILE *fp)
{
	int		mid = -1;
	const Face      *f;
	int		j;
	
	fprintf(fp, "# Face group\ng %s\n", sc->grpname[gid]);
	for (f = sc->flist; f != NULL; f = f->next) {
		if (f->grp != gid)
			continue;
		if (f->mat != mid)
			fprintf(fp, "usemtl %s\n", sc->matname[mid=f->mat]);
		fputc('f', fp);
		for (j = 0; j < f->nv; j++) {
			fprintf(fp, " %d/", f->v[j].vid - sc->nverts);
			if (f->v[j].tid >= 0)
				fprintf(fp, "%d/", f->v[j].tid - sc->ntex);
			else
				fputc('/', fp);
			if (f->v[j].nid >= 0)
				fprintf(fp, "%d", f->v[j].nid - sc->nnorms);
		}
		fputc('\n', fp);
	}
	fprintf(fp, "# End of face group %s\n\n", sc->grpname[gid]);
}

/* Write a .OBJ to stream, return # faces written */
int
toOBJ(Scene *sc, FILE *fp)
{
	int     i;

	if (sc == NULL || sc->nfaces <= 0 || fp == NULL)
		return(0);
	if (verbose)
		fputs(" Removing unreferenced vertices\r", stderr);
	del_unref_verts(sc);		/* clean up, first */
	if (verbose)
		fputs(" Writing vertices               \r", stderr);
	write_header(sc, fp);		/* write out header */
	write_verts(sc, fp);		/* write out vertex lists */
					/* write out each object group */
	for (i = 0; i < sc->ngrps; i++) {
		if (verbose)
			fprintf(stderr, " Writing faces %s               \r",
					sc->grpname[i]);
		write_group(sc, i, fp);
	}
	if (fflush(fp) < 0) {
		error(SYSTEM, "Error flushing .OBJ output");
		return(-1);
	}
	return(sc->nfaces);
}

/* Write a .OBJ file, return # faces written */
int
writeOBJ(Scene *sc, const char *fspec)
{
	extern char     *progname;
	FILE		*fp;
	int		n;

	if (sc == NULL || sc->nfaces <= 0 || fspec == NULL || !*fspec)
		return(0);
#if POPEN_SUPPORT
	if (fspec[0] == '!') {
		if ((fp = popen(fspec+1, "w")) == NULL) {
			sprintf(errmsg, "%s: cannot execute", fspec);
			error(SYSTEM, errmsg);
			return(-1);
		}
	} else
#endif
	if ((fp = fopen(fspec, "w")) == NULL) {
		sprintf(errmsg, "%s: cannot open for writing", fspec);
		error(SYSTEM, errmsg);
		return(-1);
	}
						/* start off header */
	fprintf(fp, "# Wavefront .OBJ file created by %s\n#\n", progname);
	n = toOBJ(sc, fp);			/* write scene */
#if POPEN_SUPPORT
	if (fspec[0] == '!') {
		if (pclose(fp)) {
			sprintf(errmsg, "%s: error writing to command\n", fspec);
			error(SYSTEM, errmsg);
			return(-1);
		}
	} else
#endif
	fclose(fp);				/* close it (already flushed) */
	return(n);
}
