/*	load 3ds file
 *
 *			written by Alexander Zaprjagaev
 *			frustum@public.tsu.ru
 *
 *	usage:
 *	float *load_3ds(char *name,int *num_vertex);
 *	1,2,3 - xyz
 *	4,5,6 - normal
 *	7,8 - st
 */

#include <math.h>
#include <stdio.h>
#include <malloc.h>

#define CHUNK_MAIN		0x4d4d
#define CHUNK_OBJMESH	0x3d3d
#define CHUNK_OBJBLOCK	0x4000
#define CHUNK_TRIMESH	0x4100
#define CHUNK_VERTLIST	0x4110
#define CHUNK_FACELIST	0x4120
#define CHUNK_MAPLIST	0x4140
#define CHUNK_SMOOLIST	0x4150

typedef struct {
	float x,y,z;
} vector_t;

typedef struct {
	float u,v;
} uvmap_t;

typedef struct {
	vector_t *vertex;
	int num_vertex;
	uvmap_t *uvmap;
	int num_uvmap;
	int *face;
	int *smoothgroup;
	vector_t *normal;
	int num_face;
} trimesh_t;

typedef struct {
	trimesh_t *trimesh;
	int num_trimesh;
} mesh_t;

typedef int (*process_chunk)(FILE *file,
		unsigned short type,int size,void *data);

/*
 */
static void vector_copy(vector_t *a,vector_t *b) {
	b->x = a->x;
	b->y = a->y;
	b->z = a->z;
}

static void vector_add(vector_t *a,vector_t *b,vector_t *c) {
	c->x = a->x + b->x;
	c->y = a->y + b->y;
	c->z = a->z + b->z;
}

static void vector_sub(vector_t *a,vector_t *b,vector_t *c) {
	c->x = a->x - b->x;
	c->y = a->y - b->y;
	c->z = a->z - b->z;
}

static void vector_cross(vector_t *a,vector_t *b,vector_t *c) {
	c->x = a->y * b->z - a->z * b->y;
	c->y = a->z * b->x - a->x * b->z;
	c->z = a->x * b->y - a->y * b->x;
}

static float vector_normalize(vector_t *a,vector_t *b) {
	float length = sqrt(a->x * a->x + a->y * a->y + a->z * a->z);
	if(length) {
		float ilength = 1.0 / length;
		b->x = a->x * ilength;
		b->y = a->y * ilength;
		b->z = a->z * ilength;
		return length;
	}
	b->x = 0.0;
	b->y = 0.0;
	b->z = 0.0;
	return 0.0;
}

/*
 */
static void skeep_bytes(FILE *file,int bytes) {
	fseek(file,bytes,SEEK_CUR);
}

static int skeep_string(FILE *file) {
	int i = 0;
	while(fgetc(file) != '\0') i++;
	return ++i;
}

static unsigned short read_ushort(FILE *file) {
	unsigned short ret;
	fread(&ret,1,sizeof(unsigned short),file);
	return ret;
}

static int read_int(FILE *file) {
	int ret;
	fread(&ret,1,sizeof(int),file);
	return ret;
}

static float read_float(FILE *file) {
	float ret;
	fread(&ret,1,sizeof(float),file);
	return ret;
}

static int read_chunk(FILE *file,process_chunk chunk_func,void *data) {
	unsigned short chunk_type;
	int chunk_size,content_size;
	chunk_type = read_ushort(file);
	chunk_size = read_int(file);
	content_size = chunk_size - 6;
	if(!chunk_func(file,chunk_type,content_size,data))
		skeep_bytes(file,content_size);
	return chunk_size;
}

static int read_chunks(FILE *file,int bytes,
	process_chunk chunk_func,void *data) {
	int bytes_read = 0;
	while(bytes_read < bytes)
		bytes_read += read_chunk(file,chunk_func,data);
	if(bytes_read != bytes)
		fprintf(stderr,"expected %d bytes but read %d\n",bytes_read,bytes);
	return bytes_read;
}

/*
 */
static int process_smoolist(FILE *file,unsigned short type,
	int size,void *data) {
	int i;
	trimesh_t *trimesh = (trimesh_t*)data;
	if(type == CHUNK_SMOOLIST) {
		trimesh->smoothgroup = malloc(sizeof(int) * trimesh->num_face);
		for(i = 0; i < trimesh->num_face; i++) {
			trimesh->smoothgroup[i] = read_int(file);
		}
		return 1;
	}
	return 0;
}

static int process_trimesh(FILE *file,unsigned short type,
	int size,void *data) {
	int i;
	trimesh_t *trimesh = (trimesh_t*)data;
	if(type == CHUNK_VERTLIST) { /* vertlist */
		trimesh->num_vertex = read_ushort(file);
		trimesh->vertex = malloc(sizeof(vector_t) * trimesh->num_vertex);
		for(i = 0; i < trimesh->num_vertex; i++) {
			trimesh->vertex[i].x = read_float(file);
			trimesh->vertex[i].y = read_float(file);
			trimesh->vertex[i].z = read_float(file);
		}
		return 1;
	} else if(type == CHUNK_MAPLIST) { /* maplist */
		trimesh->num_uvmap = read_ushort(file);
		trimesh->uvmap = malloc(sizeof(uvmap_t) * trimesh->num_uvmap);
		for(i = 0; i < trimesh->num_uvmap; i++) {
			trimesh->uvmap[i].u = read_float(file);
			trimesh->uvmap[i].v = 1.0 - read_float(file);
		}
		return 1;
	} else if(type == CHUNK_FACELIST) { /* facelist */
		int bytes_left;
		trimesh->num_face = read_ushort(file);
		trimesh->face = malloc(sizeof(int) * trimesh->num_face * 3);
		for(i = 0; i < trimesh->num_face * 3; i += 3) {
			trimesh->face[i + 0] = read_ushort(file);
			trimesh->face[i + 1] = read_ushort(file);
			trimesh->face[i + 2] = read_ushort(file);
			read_ushort(file);
		}
		bytes_left = size - trimesh->num_face * sizeof(unsigned short) * 4 - 2;
		if(bytes_left > 0)
			read_chunks(file,bytes_left,process_smoolist,(void*)trimesh);
		return 1;
	}
	return 0;
}

static int process_objblock(FILE *file,unsigned short type,
	int size,void *data) {
	mesh_t *mesh = (mesh_t*)data;
	if(type == CHUNK_TRIMESH) {
		mesh->num_trimesh++;
		if(mesh->trimesh == NULL) mesh->trimesh = malloc(sizeof(trimesh_t));
		else mesh->trimesh = realloc(mesh->trimesh,
			sizeof(trimesh_t) * mesh->num_trimesh);
		memset(&mesh->trimesh[mesh->num_trimesh - 1],0,sizeof(trimesh_t));
		read_chunks(file,size,process_trimesh,
			(void*)&mesh->trimesh[mesh->num_trimesh - 1]);
		return 1;
	}
	return 0;
}

static int process_objmesh(FILE *file,unsigned short type,
	int size,void *data) {
	if(type == CHUNK_OBJBLOCK) {
		size -= skeep_string(file);
		read_chunks(file,size,process_objblock,data);
		return 1;
	}
	return 0;
}

static int process_main(FILE *file,unsigned short type,
	int size,void *data) {
	if(type == CHUNK_OBJMESH) {
		read_chunks(file,size,process_objmesh,data);
		return 1;
	}
	return 0;
}


/*
 */
static void create_trimesh(trimesh_t *trimesh) {
	int i;
	uvmap_t *uvmap;
	vector_t *vertex,*normal_face,*normal_vertex;
	int *face,*vertex_count,**vertex_face,*smoothgroup;
	/* calc normals */
	normal_face = calloc(1,sizeof(vector_t) * trimesh->num_face);
	normal_vertex = calloc(1,sizeof(vector_t) * trimesh->num_face * 3);
	vertex_count = calloc(1,sizeof(int) * trimesh->num_face * 3);
	vertex_face = calloc(1,sizeof(int*) * trimesh->num_face * 3);
	trimesh->normal = normal_vertex;
	vertex = trimesh->vertex;
	face = trimesh->face;
	smoothgroup = trimesh->smoothgroup;
	for(i = 0; i < trimesh->num_face; i++) {
		int j = i * 3;
		vector_t a,b;
		int v0 = face[j + 0];
		int v1 = face[j + 1];
		int v2 = face[j + 2];
		vertex_count[v0]++;
		vertex_count[v1]++;
		vertex_count[v2]++;
		vector_sub(&vertex[v1],&vertex[v0],&a);
		vector_sub(&vertex[v2],&vertex[v0],&b);
		vector_cross(&a,&b,&normal_face[i]);
		vector_normalize(&normal_face[i],&normal_face[i]);
	}
	for(i = 0; i < trimesh->num_face * 3; i++) {
		vertex_face[i] = malloc(sizeof(int) * (vertex_count[i] + 1));
		vertex_face[i][0] = vertex_count[i];
	}
	for(i = 0; i < trimesh->num_face; i++) {
		int j = i * 3;
		int v0 = face[j + 0];
		int v1 = face[j + 1];
		int v2 = face[j + 2];
		vertex_face[v0][vertex_count[v0]--] = i;
		vertex_face[v1][vertex_count[v1]--] = i;
		vertex_face[v2][vertex_count[v2]--] = i;
	}
	for(i = 0; i < trimesh->num_face; i++) {
		int j = i * 3,k;
		int v0 = face[j + 0];
		int v1 = face[j + 1];
		int v2 = face[j + 2];
		for(k = 1; k <= vertex_face[v0][0]; k++) {
			int l = vertex_face[v0][k];
			if(i == l || (smoothgroup && smoothgroup[i] & smoothgroup[l]))
				vector_add(&normal_vertex[j + 0],&normal_face[l],
					&normal_vertex[j + 0]);
		}
		for(k = 1; k <= vertex_face[v1][0]; k++) {
			int l = vertex_face[v1][k];
			if(i == l || (smoothgroup && smoothgroup[i] & smoothgroup[l]))
				vector_add(&normal_vertex[j + 1],&normal_face[l],
					&normal_vertex[j + 1]);
		}
		for(k = 1; k <= vertex_face[v2][0]; k++) {
			int l = vertex_face[v2][k];
			if(i == l || (smoothgroup && smoothgroup[i] & smoothgroup[l]))
				vector_add(&normal_vertex[j + 2],&normal_face[l],
					&normal_vertex[j + 2]);
		}
	}
	for(i = 0; i < trimesh->num_face * 3; i++) {
		vector_normalize(&normal_vertex[i],&normal_vertex[i]);
		free(vertex_face[i]);
	}
	free(normal_face);
	free(vertex_count);
	free(vertex_face);
	/* create linear arrays for vertex and uvmap */
	vertex = calloc(1,sizeof(vector_t) * trimesh->num_face * 3);
	uvmap = calloc(1,sizeof(uvmap_t) * trimesh->num_face * 3);
	for(i = 0; i < trimesh->num_face * 3; i += 3) {
		vector_copy(&trimesh->vertex[trimesh->face[i + 0]],&vertex[i + 0]);
		vector_copy(&trimesh->vertex[trimesh->face[i + 1]],&vertex[i + 1]);
		vector_copy(&trimesh->vertex[trimesh->face[i + 2]],&vertex[i + 2]);
	}
	free(trimesh->vertex);
	trimesh->vertex = vertex;
	trimesh->num_vertex = trimesh->num_face * 3;
	if(trimesh->uvmap) {
		for(i = 0; i < trimesh->num_face * 3; i += 3) {
			uvmap[i + 0].u = trimesh->uvmap[trimesh->face[i + 0]].u;
			uvmap[i + 0].v = trimesh->uvmap[trimesh->face[i + 0]].v;
			uvmap[i + 1].u = trimesh->uvmap[trimesh->face[i + 1]].u;
			uvmap[i + 1].v = trimesh->uvmap[trimesh->face[i + 1]].v;
			uvmap[i + 2].u = trimesh->uvmap[trimesh->face[i + 2]].u;
			uvmap[i + 2].v = trimesh->uvmap[trimesh->face[i + 2]].v;
		}
		free(trimesh->uvmap);
	}
	trimesh->uvmap = uvmap;
	trimesh->num_uvmap = trimesh->num_face * 3;
	free(trimesh->face);
	trimesh->face = NULL;
}

static float *create_mesh(mesh_t *mesh,int *num_vertex) {
	int i,j;
	float *vertex;
	for(i = 0, *num_vertex = 0; i < mesh->num_trimesh; i++) {
		create_trimesh(&mesh->trimesh[i]);
		*num_vertex += mesh->trimesh[i].num_face;
	}
	*num_vertex *= 3;
	vertex = malloc(sizeof(float) * *num_vertex * 8);
	for(i = 0, j = 0; i < mesh->num_trimesh; i++) {
		int k;
		for(k = 0; k < mesh->trimesh[i].num_face * 3; k++, j += 8) {
			vertex[j + 0] = mesh->trimesh[i].vertex[k].x;
			vertex[j + 1] = mesh->trimesh[i].vertex[k].y;
			vertex[j + 2] = mesh->trimesh[i].vertex[k].z;
			vertex[j + 3] = mesh->trimesh[i].normal[k].x;
			vertex[j + 4] = mesh->trimesh[i].normal[k].y;
			vertex[j + 5] = mesh->trimesh[i].normal[k].z;
			vertex[j + 6] = mesh->trimesh[i].uvmap[k].u;
			vertex[j + 7] = mesh->trimesh[i].uvmap[k].v;
		}
		if(mesh->trimesh[i].vertex) free(mesh->trimesh[i].vertex);
		if(mesh->trimesh[i].normal) free(mesh->trimesh[i].normal);
		if(mesh->trimesh[i].uvmap) free(mesh->trimesh[i].uvmap);
		if(mesh->trimesh[i].face) free(mesh->trimesh[i].face);
		if(mesh->trimesh[i].smoothgroup) free(mesh->trimesh[i].smoothgroup);
	}
	free(mesh->trimesh);
	return vertex;
}

/*
 */
float *load_3ds(char *name,int *num_vertex) {
	FILE *file;
	mesh_t *mesh;
	float *vertex;
	unsigned short chunk_type;
	int chunk_size,content_size;
	file = fopen(name,"rb");
	if(!file) {
		fprintf(stderr,"error open %s file\n",name);
		return NULL;
	}
	chunk_type = read_ushort(file);
	chunk_size = read_int(file);
	content_size = chunk_size - 6;
	if(chunk_type != CHUNK_MAIN) {
		fprintf(stderr,"wrong main chunk\n");
		fclose(file);
		return NULL;
	}
	mesh = calloc(1,sizeof(mesh_t));
	read_chunks(file,content_size,process_main,(void*)mesh);
	fclose(file);
	vertex = create_mesh(mesh,num_vertex);
	free(mesh);
	return vertex;
}
