code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "window.h"
#include "gui.h"
#include "hash.h"

#include "utilities.h"

VEC(GUIText*) gui_list;

HashTable* font_cache; // TextRes*

ShaderProgram* textProg;

/*
	//arial = LoadFont("Arial", 32, NULL);
	glerr("clearing before text program load");

	printf("text prog %d\n", textProg->id);
	unsigned int colors[] = {
		0xFF0000FF, 2,
		0x00FF00FF, 4,
		0x0000FFFF, INT_MAX
	};

	//strRI = prepareText(arial, "FPS: --", -1, colors);
	strRI = prepareText(arialsdf, "FPS: --", -1, colors);

		snprintf(frameCounterBuf, 128, "dtime:  %.2fms", sdtime);
// 		snprintf(frameCounterBuf, 128, "dtime:  %.2fms", gs->perfTimes.draw * 1000);

		//printf("--->%s\n", frameCounterBuf);

		updateText(strRI, frameCounterBuf, -1, fpsColors);
		*/

// handles caching in ram and on disk
static TextRes* getFont(char* name) {
	TextRes* tr;
	char* filename;

	// try the cache first
	if(HT_get(font_cache, name, &tr)) {
		return tr;
	}

	// not in the cache
	filename = malloc(strlen(name) + strlen(".sdf") + 1);
	filename[0] = 0;
	strcpy(filename, name);
	strcat(filename, ".sdf");

	// try to load a pre-generated sdf file
	tr = LoadSDFFont(filename);
	if(tr == NULL) {
		tr = GenerateSDFFont(name, 16, NULL);
		SaveSDFFont(filename, tr);
	}

	HT_set(font_cache, name, tr);

	free(filename);

	return tr;
}

void gui_Init() {

	//init font cache
	font_cache = HT_create(2);

	textProg = loadCombinedProgram("guiTextSDF");

}

GUIText* guiTextNew(char* str, Vector* pos, float size, char* fontname) {

	GUIText* gt;

	unsigned int colors[] = {
		0x88FF88FF, INT_MAX
	};

	gt = calloc(1, sizeof(*gt));
	CHECK_OOM(gt);

	if(pos) vCopy(pos, &gt->pos);
	gt->size = size;

	gt->font = getFont(fontname);
	if(!gt->font) {
		printf(stderr, "Failed to load font: %s\n", fontname);
	}

	if(str) {
		gt->current = strdup(str);
		gt->strRI = prepareText(gt->font, str, -1, colors);
	}

	VEC_PUSH(&gui_list, gt);

	return gt;
}

void guiDelete(GUIHeader* gh) {
	gh->deleted = 1;
}

void guiTextSetValue(GUIText* gt, char* newval) {
	unsigned int colors[] = {
		0x88FF88FF, INT_MAX
	};

	if(0 != strcmp(newval, gt->current)) {
		if(gt->current) free(gt->current);
		gt->current = strdup(newval);

		updateText(gt->strRI, newval, -1, colors);
	}
}

void gui_RenderAll(GameState* gs) {
	int i;

	for(i = 0; i < VEC_LEN(&gui_list); i++) {

		guiTextRender(VEC_DATA(&gui_list)[i], gs);
	}
}

void guiTextRender(GUIText* gt, GameState* gs) {

	Matrix textProj;

	MatrixStack textModel;

	msAlloc(3, &textModel);

	glUseProgram(textProg->id);

	// text stuff
	textProj = IDENT_MATRIX;

	mOrtho(0, gs->screen.aspect, 0, 1, -1, 100, &textProj);
	//mScale3f(.5,.5,.5, &textProj);

	msIdent(&textModel);
	msScale3f(gt->size * .01, gt->size* .01, gt->size * .01, &textModel);
	msTransv(&gt->pos, &textModel);

	GLuint tp_ul = glGetUniformLocation(textProg->id, "mProj");
	GLuint tm_ul = glGetUniformLocation(textProg->id, "mModel");
	GLuint ts_ul = glGetUniformLocation(textProg->id, "fontTex");

	GLuint lower_ul = glGetUniformLocation(textProg->id, "smoothLower");
	GLuint upper_ul = glGetUniformLocation(textProg->id, "smoothUpper");
	GLuint limit_ul = glGetUniformLocation(textProg->id, "smoothLimit");

	glUniform1f(lower_ul, font_smooth_lower);
	glUniform1f(upper_ul, font_smooth_upper);
	glUniform1f(limit_ul, font_smooth_limit);

	glUniformMatrix4fv(tp_ul, 1, GL_FALSE, textProj.m);
	glUniformMatrix4fv(tm_ul, 1, GL_FALSE, msGetTop(&textModel)->m);
	glexit("text matrix uniforms");

	glDisable(GL_CULL_FACE);

	glActiveTexture(GL_TEXTURE0);
	glexit("active texture");

	glUniform1i(ts_ul, 0);
	glexit("text sampler uniform");
// 	glBindTexture(GL_TEXTURE_2D, arial->textureID);
	glBindTexture(GL_TEXTURE_2D, gt->font->textureID); // TODO check null ptr
	glexit("bind texture");

	glBindVertexArray(gt->strRI->vao);
	glexit("text vao bind");

	glBindBuffer(GL_ARRAY_BUFFER, gt->strRI->vbo);
	glexit("text vbo bind");
	glDrawArrays(GL_TRIANGLES, 0, gt->strRI->vertexCnt);
	glexit("text drawing");

}


code:
#ifndef __MV_GUI_H__
#define __MV_GUI_H__

#include "common_gl.h"
#include "common_math.h"

#include "text/text.h"
#include "game.h"

extern float font_smooth_lower;
extern float font_smooth_upper;
extern float font_smooth_limit;

// temp shit

typedef struct {
	Vector pos;
	float size;

	char hidden;
	char deleted;

} GUIHeader;

typedef struct {
	GUIHeader header;

	char* current;

	Vector pos;
	float size;

	// align, height, width wrapping

	TextRes* font;
	TextRenderInfo* strRI;

} GUIText;

GUIText* guiTextNew(char* str, Vector* pos, float size, char* fontname);
void gui_Init();
void gui_RenderAll(GameState* gs);

void guiTextRender(GUIText* gt, GameState* gs);
void guiTextDelete(GUIText* gt);
void guiTextSetValue(GUIText* gt, char* newval);

#endif // __MV_GUI_H__

license: mit


code:
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include "MurmurHash3.h"
#include "hash.h"

#define MURMUR_SEED 718281828

static size_t find_bucket(HashTable* obj, uint64_t hash, void* key);
int HT_CStringCompareFn(void* a, void* b);

void HT_init(HashTable* ht, int allocPOT) {
	int pot, allocSz;

	pot = allocPOT < 4 ? 4 : allocPOT;

	ht->hashFn = &HT_CStringHashFn;
	ht->keyCompareFn = &HT_CStringCompareFn;
	ht->fill = 0;
	ht->alloc_size = 1 << pot;
	ht->grow_ratio = 0.75f;
	ht->shrink_ratio = 99.0f; // set greater than 1.0 to entirely disable
	ht->buckets = calloc(1, sizeof(*ht->buckets) * ht->alloc_size);
	if(!ht->buckets) {
		free(ht);
		return NULL;
	}
}

HashTable* HT_create(int allocPOT) {

	HashTable* obj;

	obj = malloc(sizeof(*obj));
	if(!obj) return NULL;

	HT_init(obj, allocPOT);

	return obj;
}

HashTable* HT_createCustom(int allocPOT, hashFn_t hashFn, keyCompareFn_t keyCompareFn) {
	HashTable* obj;

	obj = HT_create(allocPOT);
	obj->hashFn = hashFn;
	obj->keyCompareFn = keyCompareFn;

	return obj;
}

void HT_destroy(HashTable* obj, int free_values_too) {
	int i, n;

	if(free_values_too) {
		for(i = 0, n = 0; i < obj->alloc_size, n < obj->fill; i++) {
			// only free valid pointers that also have a key
			// deleted items are assumed to be cleaned up by the user
			if(obj->buckets[i].key) {
				if(obj->buckets[i].value) free(obj->buckets[i].value);
				n++;
			}
		}
	}

	if(obj->buckets) free(obj->buckets);
	free(obj);
}

// uses a truncated 128bit murmur3 hash
uint64_t HT_HashFn(void* key, size_t len) {
	uint64_t hash[2];

	// len is optional
	if(len == -1) len = strlen(key);

	MurmurHash3_x64_128(key, len, MURMUR_SEED, hash);

	return hash[0];
}

uint64_t HT_CStringHashFn(void* key) {
	return HT_HashFn(key, -1);
}

int HT_CStringCompareFn(void* a, void* b) {
	return strcmp(a, b);
}

static size_t find_bucket(HashTable* obj, uint64_t hash, void* key) {
	size_t startBucket, bi;

	bi = startBucket = hash % obj->alloc_size;

	do {
		struct hash_bucket* bucket;

		bucket = &obj->buckets[bi];

		// empty bucket
		if(bucket->key == NULL) {
			return bi;
		}

		if(bucket->hash == hash) {
			if(!(*obj->keyCompareFn)(key, bucket->key)) {
				// bucket is the right one and contains a value already
				return bi;
			}

			// collision, probe next bucket
		}

		bi = (bi + 1) % obj->alloc_size;
	} while(bi != startBucket);

	// should never reach here if the table is maintained properly
	return -1;
}

// should always be called with a power of two
int HT_resize(HashTable* obj, int newSize) {
	struct hash_bucket* old, *op;
	size_t oldlen = obj->alloc_size;
	size_t i, n, bi;

	old = op = obj->buckets;

	obj->alloc_size = newSize;
	obj->buckets = calloc(1, sizeof(*obj->buckets) * newSize);
	if(!obj->buckets) return 1;

	for(i = 0, n = 0; i < oldlen && n < obj->fill; i++) {
		if(op->key == NULL) continue;

		bi = find_bucket(obj, op->hash, op->key);
		obj->buckets[bi].value = op->value;
		obj->buckets[bi].hash = op->hash;
		obj->buckets[bi].key = op->key;

		n++;
	}

	free(old);

	return 0;
}

// TODO: better return values and missing key handling
// returns 0 if val is set to the value
// *val == NULL && return > 0  means the key was not found;
int HT_get(HashTable* obj, void* key, void** val) {
	uint64_t hash;
	size_t bi;

	hash = (*obj->hashFn)(key);

	bi = find_bucket(obj, hash, key);
	if(bi < 0) return 1;

	if(val) *val = obj->buckets[bi].value;
	return 0;
}

// zero for success
int HT_set(HashTable* obj, void* key, void* val) {
	uint64_t hash;
	size_t bi;

	// check size and grow if necessary
	if(obj->fill / obj->alloc_size >= obj->grow_ratio) {
		HT_resize(obj, obj->alloc_size * 2);
	}

	hash = (*obj->hashFn)(key);

	bi = find_bucket(obj, hash, key);
	if(bi < 0) return 1;

	if(obj->buckets[bi].key == NULL) {
		// new bucket
		obj->buckets[bi].key = key;
		obj->buckets[bi].hash = hash;
		obj->fill++;
	}

	obj->buckets[bi].value = val;
	obj->buckets[bi].key = key;
	obj->buckets[bi].hash = hash;

	return 0;
}

// zero for success
int HT_delete(HashTable* obj, void* key) {
	uint64_t hash;
	size_t bi, empty_bi, nat_bi;

	size_t alloc_size = obj->alloc_size;

	/* do this instead of the deletion algorithm
	// check size and shrink if necessary
	if(obj->fill / alloc_size <= obj->shrink_ratio) {
		HT_resize(obj, alloc_size > 32 ? alloc_size / 2 : 16);
		alloc_size = obj->alloc_size;
	}
	*/
	hash = (*obj->hashFn)(key);
	bi = find_bucket(obj, hash, key);

	// if there's a key, work until an empty bucket is found
	// check successive buckets for colliding keys
	//   walk forward until the furthest colliding key is found
	//   move it to the working bucket.
	//

	// nothing to delete, bail early
	if(obj->buckets[bi].key == NULL) return;

	//
	empty_bi = bi;

	do {
		bi = (bi + 1) % alloc_size;
		if(obj->buckets[bi].key == NULL) {
			//empty bucket
			break;
		}

		// bucket the hash at the current index naturally would be in
		nat_bi = obj->buckets[bi].hash % alloc_size;

		if((bi > empty_bi && // after the start
			(nat_bi <= empty_bi /* in a sequence of probed misses */ || nat_bi > bi /* wrapped all the way around */))
			||
			(bi < empty_bi && // wrapped around
			(nat_bi <= empty_bi /* in a sequence of probed misses... */ && nat_bi > bi /* ..from before the wrap */))) {

			// move this one back
			obj->buckets[empty_bi].key = obj->buckets[bi].key;
			obj->buckets[empty_bi].hash = obj->buckets[bi].hash;
			obj->buckets[empty_bi].value = obj->buckets[bi].value;

			empty_bi = bi;
		}
	} while(1);

	obj->buckets[empty_bi].key = NULL;

	return 0;
}

// iteration. no order. results undefined if modified while iterating
// returns 0 when there is none left
// set iter to NULL to start
int HT_next(HashTable* obj, void** iter, void** key, void** value) {
	struct hash_bucket* b = *iter;

	// a tiny bit of idiot-proofing
	if(b == NULL) b = &obj->buckets[-1];

	do {
		b++;
		if(b > obj->buckets + obj->alloc_size) {
			// end of the list
			*value = NULL;
			*key = NULL;
			return 0;
		}
	} while(!b->key);

	*key = b->key;
	*value = b->value;
	*iter = b;

	return 1;
}


code:
#ifndef __EACSMB_HASH_H__
#define __EACSMB_HASH_H__

#include <stdint.h>

struct hash_bucket {
	uint64_t hash;
	void* key;
	void* value;
};

typedef uint64_t (*hashFn_t)(void*);
typedef int (*keyCompareFn_t)(void*, void*);

typedef struct hash_table {
	size_t alloc_size;
	size_t fill;
	float grow_ratio; // default 0.75
	float shrink_ratio; // set greater than 1.0 to entirely disable, default 99.0
	struct hash_bucket* buckets;

	hashFn_t hashFn;
	keyCompareFn_t keyCompareFn;
} HashTable;

// NOTE: if you pass in garbage pointers you deserve the segfault

uint64_t HT_HashFn(void* key, size_t len);
uint64_t HT_CStringHashFn(void* key);

HashTable* HT_create(int allocPOT);
void HT_init(HashTable* ht, int allocPOT);
HashTable* HT_createCustom(int allocPOT, hashFn_t hashFn, keyCompareFn_t keyCompareFn);
void HT_destroy(HashTable* obj, int free_values_too);
int HT_resize(HashTable* obj, int newSize);
int HT_get(HashTable* obj, void* key, void** val);
int HT_set(HashTable* obj, void* key, void* val);
int HT_delete(HashTable* obj, void* key);

// iteration. no order. results undefined if modified while iterating
// returns 0 when there is none left
// set iter to NULL to start
int HT_next(HashTable* obj, void** iter, void** key, void** value);

#define HASH_FN_FOR_TYPE(name, x) \
uint64_t name(void* key) { \
	return HT_HashFn(key, sizeof(x)); \
}

/*
// special faster version for storing just integer sets
struct int_hash_bucket {
	uint64_t key;
	uint64_t value;
};

typedef struct int_hash_table {
	size_t alloc_size;
	size_t fill;
	float grow_ratio;
	float shrink_ratio;
	struct int_hash_bucket* buckets;
} IntHash;

*/

#endif //__EACSMB_HASH_H__


code:
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

#include "c_json/json.h"
#include "json_gl.h"
#include "hash.h"

static HashTable* lookup;

struct enum_data {
	char* k;
	GLenum v;
};

#define gl_enum_data(str, macro, val) {str, val},
// #define gl_enum_data(str, macro, val) {str, macro},
static struct enum_data enum_values_for_gl[] = {
	#include "gl_enum_data.c"
	{0,0}
};

void json_gl_init_lookup() {
	struct enum_data* d;

	if(lookup) return;
	lookup = HT_create(13); // 8192; there are 5671 enums

	d = enum_values_for_gl;

	fprintf(stderr, "initializing GLenum lookup...");
	while(d->k) {
		HT_set(lookup, d->k, d->v);
		d++;
	}
	fprintf(stderr, " done.\n");
}

int json_as_GLenum(struct json_value* v, GLenum* out) {

	switch(v->type) { // actual type
		case JSON_TYPE_UNDEFINED:
		case JSON_TYPE_NULL:
			*out = 0;
			return 1;

		case JSON_TYPE_INT: // treat it as a literal value
			*out = v->v.integer;
			return 0;

		case JSON_TYPE_DOUBLE:
			*out = v->v.dbl;  // treat it as a literal value, converted to int
			return 0;

		case JSON_TYPE_STRING: // look up the enum
			return HT_get(lookup, v->v.str, out);

		case JSON_TYPE_OBJ: // all invalid
		case JSON_TYPE_ARRAY:
		case JSON_TYPE_COMMENT_SINGLE:
		case JSON_TYPE_COMMENT_MULTI:
		default:
			*out = 0;
			return 1;
	}
}

static float index_as_float(json_value_t* obj, int index, float def) {
	json_value_t* v;
	float f;
	int ret;

	if(!json_as_float(v, &f)) {
		return def;
	}

	return f;
}

static float key_as_float(json_value_t* obj, char* key, float def) {
	json_value_t* v;
	float f;
	int ret;

	if(!json_obj_get_key(obj, key, &v)) {
		return def;
	}

	if(!json_as_float(v, &f)) {
		return def;
	}

	return f;
}

int json_as_vector(struct json_value* v, int max_len, float* out) {
	int i;
	float f;
	struct json_array_node* an;

	if(v->type == JSON_TYPE_OBJ) {
		out[0] = key_as_float(v, "x", 0.0);
		out[1] = key_as_float(v, "y", 0.0);
		out[2] = key_as_float(v, "z", 0.0);
		out[3] = key_as_float(v, "w", 1.0);

		// get x/y/z/w, r/g/b/a
	}
	else if(v->type == JSON_TYPE_ARRAY) {
		// [1,2,3,4]
		an = v->v.arr->head;
		i = 0;
		while(an) {
			json_as_float(an->value, &f);
			out[i++] = f;
			an = an->next;
		}

		switch(i < max_len - 1) out[i++] = 0.0f;
		if(max_len == 4 && i == 3) out[3] = 1.0f;
	}
	else if(v->type == JSON_TYPE_DOUBLE) {
	// duplicate numbers to every field
		json_as_float(v, &f);
		for(i = 0; i < max_len; i++) out[i] = f;
	}
}

int json_as_type_gl(struct json_value* v, enum json_type_gl t, void* out) {
	switch(t) {

	}
}


code:
#ifndef __EACSMB_JSON_GL_H__
#define __EACSMB_JSON_GL_H__

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include "c_json/json.h"

typedef enum json_type_gl {

	JSON_TYPE_GL_ENUM = JSON__type_enum_tail,

	JSON_TYPE_GL_MAXVALUE

} json_type_gl_t;

#undef JSON__type_enum_tail
#define JSON__type_enum_tail JSON_TYPE_GL_MAXVALUE

int json_as_GLenum(struct json_value* v, GLenum* out);
int json_as_vector(struct json_value* v, int max_len, float* out);

int json_as_type_gl(struct json_value* v, enum json_type_gl t, void* out);

void json_gl_init_lookup();

#endif // __EACSMB_JSON_GL_H__


code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#include <X11/X.h>
#include <X11/Xlib.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include "c3dlas/c3dlas.h"
#include "c3dlas/meshgen.h"
#include "text/text.h"

#include "utilities.h"
#include "config.h"
#include "shader.h"
#include "window.h"
#include "game.h"

static XStuff xs;
static GameState game;
static InputState input;

int main(int argc, char* argv[]) {
	int first = 1;
	int configStatus = 0;

// 	zeroConfig(&game.uSettings);
// 	configStatus = updateConfigFromFile(&game.uSettings, "defaults.ini");
// 	if(configStatus) {
// 		printf("failed to load defaults.ini [code %d]\n",configStatus);
// 	}
//
// 	configStatus = updateConfigFromFile(&game.uSettings, "settings.ini");
// 	if(configStatus) {
// 		printf("failed to load settings.ini [code %d]\n",configStatus);
// 	}
//
//
//
	loadSettings("assets/config/settings", &game);
	setGameSettings(&game.settings,&game.uSettings);

	memset(&xs, 0, sizeof(XStuff));

	xs.targetMSAA = 4;
	xs.windowTitle = "Model Viewer";

	initXWindow(&xs);

	while(1) {
		processEvents(&xs, &input, -1);

		if (first && xs.ready) {
			initGame(&xs, &game);
			first = 0;
		}

		if(xs.ready) {
			gameLoop(&xs, &game, &input);
		}

		if(game.frameSpan < 1.0/60.0) {
			// shitty estimation based on my machine's heuristics, needs improvement
			float sleeptime = (((1.0/60.0) * 1000000) - (game.frameSpan * 1000000)) * .7;
 			//printf("sleeptime: %f\n", sleeptime / 1000000);
			//sleeptime = 1000;
			if(sleeptime > 0) usleep(sleeptime); // problem... something is wrong in the math
		}
	}

	return 0;
}


code:
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <math.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include "c3dlas/c3dlas.h"
#include "c3dlas/meshgen.h"

#include "hash.h"

#include "utilities.h"
#include "objloader.h"

// returns status code
int strtol_2(char* s, int* charsRead, int* value) {
	char* end;
	int val;

	if(charsRead) *charsRead = 0;

	errno = 0;
	val = strtol(s, &end, 10);
	if(errno != 0) return errno; // read error
	if(s == end) return -1; // no characters read

	// success
	if(charsRead) *charsRead = end - s;
	if(value) *value = val;

	return 0;
}

int parseFaceVertex(char** s, int* info) {
	char* end;
	int val;
	int err, chars_read;

	while(**s && (**s == ' ' || **s == '\t' || **s == '\r')) (*s)++;
	if(**s == '\n') return 1;

	info[3] = 0;

	// first
	err = strtol_2(*s, &chars_read, &val);
	if(err) return 1;
	*s += chars_read ;
	info[0] = val;
	info[3]++;

	if(**s != '/') return 0;
	(*s)++;

	err = strtol_2(*s, &chars_read, &val);
	if(err) return 0;
	*s += chars_read ;
	info[1] = val;
	info[3]++;

	if(**s != '/') return 0;
	(*s)++;

	err = strtol_2(*s, &chars_read, &val);
	if(err) return 0;
	*s += chars_read ;
	info[2] = val;
	info[3]++;

	return 0;
}

static uint64_t vertexLookupHash(void* key) {
	return HT_HashFn(key, sizeof(OBJVertex));
}

static int vertexLookupCmp(void* a, void* b) {
	return memcmp(a, b, sizeof(OBJVertex));
}

static inline uint64_t makeEdgeKey(uint32_t a, uint32_t b) {
	return MIN(a, b) | MAX(a, b) << 32;
}

void loadOBJFile(char* path, int four_d_verts, OBJContents* contents) {

	char* f, *raw;
	int line = 0;
	int cur_pos = 0;
	int cur_norm = 0;
	int cur_tex = 0;
	int cur_param = 0;

	int current_vertex_index = 0;

	raw = readFile(path, NULL);
	if(!raw) return;

	memset(contents, 0, sizeof(OBJContents));

	int numVertices = 0;
	int numNormals = 0;
	int numTexCoords = 0;
	int numFaces = 0;

	// calculate how much storage is needed
	f = raw;
	while(*f) {
		char c;

		c = *f;
		f++;

		if(c == 'f') { // faces
			//numFaces++;
// 			count number of flip-flops between space and not, subtract two
			{
				int in_letters = 0;
				while(*f && f++) {
					if(in_letters && isspace(*f)) {
						in_letters = 0;
						//printf("face");
						numFaces++;
					}
					if(!in_letters && !isspace(*f)) {
						in_letters = 1;
					}

					if(*f == '\n') break;
					//printf("x");
				}
				//printf("-\n");
			//while(*f++ != '\n');
			}
		}
		else if(c == 'v') {
			c = *f;
			if(c == ' ') numVertices++;
			else if(c == 'n') numNormals++;
			else if(c == 't') numTexCoords++;
		}

		while(*f && *f != '\n') f++;
		f++;
	}

	printf("Vertices: %d\n", numVertices);
	printf("Normals: %d\n", numNormals);
	printf("TexCoords: %d\n", numTexCoords);
	printf("Faces: %d\n", numFaces);

	HashTable* vertexLookup;
	vertexLookup = HT_createCustom(4, vertexLookupHash, vertexLookupCmp);

	Vector* vertices;
	Vector* normals;
	Vector2* texCoords;
	OBJVertex* faces;
	int* indices;

	int vc = 0;
	int nc = 0;
	int tc = 0;
	int fc = 0;
	int ic = 0;

	vertices = malloc(numVertices * sizeof(*vertices));
	normals = malloc(numNormals * sizeof(*normals));
	texCoords = malloc(numTexCoords * sizeof(*texCoords));
	// double the size in case of quads
	faces = malloc(numFaces * 6 * sizeof(*faces));
	indices = malloc(numFaces * 3 * sizeof(*indices));

	// read the actual data
	f = raw;
	while(*f) {
		char c;
		int chars_read, n, j;
		int fd[3][4];
		int vertex_count;
		int ret;
		int nn = 0;

		//printf("looping \n");
		c = *f;
		f++;

		if(c == 'f') { // faces. currently only triangles and quite hacky
			//  f v[/t[/n]] v[/t[/n]] v[/t[/n]] [v[/t[/n]]]

			//printf("parseface \n");

			ret = parseFaceVertex(&f, &fd[0]); // the center vertex
			//if(!ret)
			//printf("ret 1: %d, ", ret);
			ret = parseFaceVertex(&f, &fd[1]); // the next vertex
			//printf("ret 2: %d, ", ret);

			do {

				ret = parseFaceVertex(&f, &fd[2]);
				//printf("ret 3: %d \n", ret);
				//printf("parseface( %d )\n", ret);
				if(ret) break;

				//parseFaceVertex(&f, &fd[3]);

				/*
				printf("%d/%d/%d[%d] %d/%d/%d[%d] %d/%d/%d[%d] \n",
					fd[0][0], fd[0][1], fd[0][2], fd[0][3],
					fd[1][0], fd[1][1], fd[1][2], fd[1][3],
					fd[2][0], fd[2][1], fd[2][2], fd[2][3]);
				*/

				//printf("fc %d %d\n", fc, fd[0][0]);
				//indices[ic++] = fd[0][];
				vCopy(  &vertices[fd[0][0]-1], &faces[fc].v);
				vCopy2(&texCoords[fd[0][1]-1], &faces[fc].t);
				vCopy(   &normals[fd[0][2]-1], &faces[fc].n);
				fc++;

				vCopy(  &vertices[fd[1][0]-1], &faces[fc].v);
				vCopy2(&texCoords[fd[1][1]-1], &faces[fc].t);
				vCopy(   &normals[fd[1][2]-1], &faces[fc].n);
				fc++;

				vCopy(  &vertices[fd[2][0]-1], &faces[fc].v);
				vCopy2(&texCoords[fd[2][1]-1], &faces[fc].t);
				vCopy(   &normals[fd[2][2]-1], &faces[fc].n);
				fc++;
				//printf("fc %d\n", fc);
				// shift the last vertex

				memcpy(&fd[1], &fd[2], sizeof(fd[2]));
			} while(1);

		}
		else if(c == 'v') {

			c = *f;
			f++;
			if(c == ' ') {
				chars_read = 0;
				n = sscanf(f,
					" %f %f %f%n",
					&vertices[vc].x,
					&vertices[vc].y,
					&vertices[vc].z,
					&chars_read);

				if(n < 3) {
					fprintf(stderr, "error reading vertex\n");
				}

				//printf("got vertex: %f %f %f ",vertices[vc].x,vertices[vc].y,vertices[vc].z);

				f += chars_read-1;
				//printf("'%.5s' \n", (f-2));
				vc++;
			}
			else if(c == 'n') {
				chars_read = 0;
				n = sscanf(f,
					" %f %f %f%n",
					&normals[nc].x,
					&normals[nc].y,
					&normals[nc].z,
					&chars_read);

				if(n < 3) {
					fprintf(stderr, "error reading normal\n");
				}

				f += chars_read-1;
				nc++;
			}
			else if(c == 't') {
				chars_read = 0;
				n = sscanf(f,
					" %f %f%n",
					&texCoords[tc].x,
					&texCoords[tc].y,
					&chars_read);

				if(n < 2) {
					fprintf(stderr, "error reading tex coord\n");
				}

				//printf("got tex coords: %f %f\n", texCoords[tc].x, texCoords[tc].y);

				f += chars_read-1;
				tc++;
			}

			f++;
			//printf("x");
		}

		// skip to the end
		while(*f && *f != '\n') f++;
		f++;
	}

	contents->faces = faces;
	contents->faceCnt = fc  > 0 ? fc / 3 : 0;

	contents->indexCnt = fc;
}

/*

static int sgrabfloat(char** s, float* f) {
	int num = 0;

	if(sscanf(*s, " %f%n", f, &num) > 0) {
		*s += num;
		return 1;
	}

	return 0;
}

// all real indices are 1-based. zero means not present.
static int readFaceVertex(char** s, int* v, int* vn, int* vt) {
	//  8/1/2
	// pos/normal/tex

	int n, cs;

	*v = 0;
	*vn = 0;
	*vt = 0;
	//printf(*s);
	n = sscanf(*s, " %d%n", v, &cs);
	*s += cs;
	if(**s != '/') {
		printf("broken face vertex - 1  '%.3s' \n", *s);
	}
	if(n < 1) {
		printf("missing face vertex position\n");
	}
	cs = 0;

	(*s)++;
	n = sscanf(*s, "%d%n", vn, &cs);
	*s += cs;
	if(**s != '/') {
		printf("broken face vertex - 2 %d  '%.3s' \n", n, *s);
	}
	if(n < 1) {
		printf("no face vertex normal\n");
	}
	cs = 0;

	(*s)++;
	if(isspace(**s)) return;
	n = sscanf(*s, "%d%n", vt, &cs);
	*s += cs;
	if(n < 1) {
		printf("no face vertex texture\n");
	}

}

// all real indices are 1-based. zero means not present.
static int readFace(char** s, int* v, int* vn, int* vt) {

	if(**s == 'f') {
		(*s)++;
		//printf(*s);
		readFaceVertex(s, v, vn, vt);
		readFaceVertex(s, v + 1, vn + 1, vt + 1);
		readFaceVertex(s, v + 2, vn + 2, vt + 2);
	}

}

static void skip_line(char** s) {
	if(!*s) return;
	while(**s && **s != '\n') (*s)++;
	while(**s && **s == '\n') (*s)++;
}

// kinda shitty limited version. object names longer than 1023 chars are too fucking long anyway.
static char* strscandup(FILE* f) {
	char buf[1024];

	buf[0] = 0;

	fscanf(f, "%s", buf);
	return strdup(buf);
}

static void append_vector(OBJDataBuffer* c, float* fs) {

	if(!c->buf) {
		c->sz = 64;
		c->buf = malloc(c->sz * c->dims * sizeof(float));
	}

	if(c->cnt >= c->sz) {
		c->sz *= 2;
		c->buf = realloc(c->buf, c->sz * c->dims * sizeof(float));
	}

	memcpy(&c->buf[c->cnt * c->dims], fs, c->dims * sizeof(float));

	c->cnt += c->dims;
}

void old_loadOBJFile(char* path, int four_d_verts, OBJContents* contents) {

	char* f;
	int line = 0;
	int cur_pos = 0;
	int cur_norm = 0;
	int cur_tex = 0;
	int cur_param = 0;

	int current_vertex_index = 0;

	f = readFile(path, NULL);
	if(!f) return;

	memset(contents, 0, sizeof(OBJContents));
	contents->v.dims = four_d_verts ? 4 : 3;

	// count everything

	// alloc everything

	// read each thing into the mesh

	//HACk BUG
	contents->f = malloc(sizeof(OBJVertex) * 1337);

	while(*f) {
		char c;
		int n, j;
		float fbuf[4] = { 0.0, 0.0, 0.0, 1.0 };

		//printf("looping \n");
		c = *f;
		f++;
		printf("-%d-%c- ", line, c);

		// skip comments, usemtl, mtllib, and smoothing lines
		if(c == '#' || c == 'm' || c == 'u' || c == 's') {
			printf("useless line '%c'\n", c);
			skip_line(&f);
			line++;
			continue;
		}

		// poly group name, unsupported as of now
		if(c == 'g') {
			printf("polygroup \n");
			skip_line(&f);
			line++;
			continue;
		}

		if(c == 'f') { // faces; triangles or quads
			//  f v[/t[/n]] v[/t[/n]] v[/t[/n]] [v[/t[/n]]]
			int V[3];
			int Vn[3];
			int Vt[3];

			int vi, ci;

			int i;

			readFace(&f, V, Vn, Vt);

			for(i = 0; i < 3; i++) {
				if(V[i] < 0) V[i] = current_vertex_index + 1 + V[i];
				else V[i] = V[i] == 0 ? 0 : -V[i];
			}

			for(i = 0; i < 3; i++) {
				if(Vn[i] < 0) Vn[i] = current_vertex_index + 1 + Vn[i];
				else Vn[i] = Vn[i] == 0 ? 0 : -Vn[i];
			}

			for(i = 0; i < 3; i++) {
				if(Vt[i] < 0) Vt[i] = current_vertex_index + 1 + Vt[i];
				else Vt[i] = Vt[i] == 0 ? 0 : -Vt[i];
			}

			// all indices are 1-based; convert to 0-based
			// resolve negative (relative) indices to absolute indices
			contents->f[contents->fv_cnt].v  = V[0];
			contents->f[contents->fv_cnt].vn = Vn[0];
			contents->f[contents->fv_cnt].vt = Vt[0];
			contents->fv_cnt++;
			contents->f[contents->fv_cnt].v  = V[0];
			contents->f[contents->fv_cnt].vn = Vn[0];
			contents->f[contents->fv_cnt].vt = Vt[0];
			contents->fv_cnt++;
			contents->f[contents->fv_cnt].v  = V[0];
			contents->f[contents->fv_cnt].vn = Vn[0];
			contents->f[contents->fv_cnt].vt = Vt[0];
			contents->fv_cnt++;
			printf("found face #%d [] \n", contents->fv_cnt/3);

			printf(" face \n");
			skip_line(&f);
			line++;
			continue;

			/*
			if(vi > 2) { // triangulate the quad
				contents->f[f_cnt++] = ind[0][0] - 1;
				contents->f[f_cnt++] = ind[0][2] - 1;
				contents->f[f_cnt++] = ind[0][3] - 1;
			}
			* /
		}

		// vertex data
		else if(c == 'v') {
			c = *f;
			f++;
			printf(" next c '%c' \n ", c);

			//fbuf ;
			n = 0;

			skip_line(&f);
			line++;
			continue;

			sgrabfloat(&f, &fbuf);
			/*
			char chars = 0; l
			do {
				j = sscanf(f, " %f%n", &fbuf[j++], &chars);
				f += chars;
			} while(j);
			* /
			if(c == ' ') { // position
				if(n < 3) {
					fprintf(stderr, "OBJ: only %d coordinates for vertex on line %d\n", n, line);
					return;
				}

				append_vector(&contents->v, fbuf);

				printf("found vertex #%d [%f, %f, %f] \n", contents->v.cnt/contents->v.dims, fbuf[0], fbuf[1], fbuf[2]);
			}
			else if(c == 'n') { // normal

				// normalize?

				append_vector(&contents->vn, fbuf);
				printf("found normal #%d [] \n", contents->vn.cnt);
			}
			else if(c == 't') { // texture coordinates
				append_vector(&contents->vt, fbuf);
				printf("found texcoords #%d [] \n", contents->vt.cnt);
			}
// 			else if(c == 'p') { // "freeform geometry coordinates"
// 				append_vector(&contents->vp, fbuf);
// 			}
			else {
				printf("found unknown v '%c'\n", c);
			}

			skip_line(&f);
			line++;
			continue;
		}

		// object name
		else if(c == 'o') {
			printf("object name\n");
			contents->o = strlndup(f);
			skip_line(&f);
			line++;
			continue;
		}
		else {
			printf("nothin\n");

		}

	}
}
*/
/*
Mesh* OBJtoMesh(OBJContents* obj) {

	Mesh* m;
	int i;

	m = calloc(1, sizeof(Mesh));

	// vertex arrays are the number of unique vertices. obj's do not enforce this.
	// all vertices are created new for simplicity sake
	m->szVertices = obj->fv_cnt;
	m->vertexCnt = obj->fv_cnt;
	m->indices = malloc(sizeof(obj->fv_cnt) * sizeof(unsigned short));
	m->vertices = malloc(sizeof(MeshVertex) * obj->fv_cnt);

	for(i = 0; i < obj->fv_cnt; i++) {
		vCopy(&obj->v.buf[obj->f[i].v], &m->vertices[i].v);
		vCopy(&obj->vn.buf[obj->f[i].vn], &m->vertices[i].n);
		m->vertices[i].t.u = obj->vn.buf[obj->f[i].vt];
		m->vertices[i].t.v = obj->vn.buf[obj->f[i].vt+1];
		m->indices[i] = i;
	}

	return m;
}

*/


code:
#ifndef __EACSMB_OBJLOADER_H__
#define __EACSMB_OBJLOADER_H__

typedef struct OBJDataBuffer {
	float* buf;
	int cnt, sz, dims;
} OBJDataBuffer;

typedef struct OBJVertex { // Face vertex
	Vector v, n;
	Vector2 t;
} OBJVertex;

typedef struct OBJContents {

	// only supports one mesh atm
	OBJVertex* faces;
	int faceCnt;

	// always converted to GL_TRIANGLES
	int indexCnt;
	int* indices;
	int vertexCnt;
	OBJVertex* vertices;

	char* o;
} OBJContents;

void loadOBJFile(char* path, int four_d_verts, OBJContents* contents);

#endif // __EACSMB_OBJLOADER_H__


code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libgen.h>

#include "plyloader.h"

#define checkstr(a, b) (0 == strncmp(a, b, strlen(b)))
#define checkstrcase(a, b) (0 == strncasecmp(a, b, strlen(b)))

static void parseVertices(PLYContents* pc, ply_elem* e, char** s);
static void parseFaces(PLYContents* pc, ply_elem* e, char** s);

PLYContents* allocPLYContents() {
	PLYContents* pc;

	pc = calloc(1, sizeof(*pc));
	CHECK_OOM(pc);

	VEC_INIT(&pc->comments);
	VEC_INIT(&pc->vertices);
	VEC_INIT(&pc->colors);
	VEC_INIT(&pc->normals);
	VEC_INIT(&pc->texcoords);
	VEC_INIT(&pc->faces);
	VEC_INIT(&pc->indicesTex);
	VEC_INIT(&pc->elements);

	return pc;
}

void destroyPLYContents(PLYContents* pc) {

	VEC_FREE(&pc->comments);
	VEC_FREE(&pc->vertices);
	VEC_FREE(&pc->colors);
	VEC_FREE(&pc->normals);
	VEC_FREE(&pc->texcoords);
	VEC_FREE(&pc->faces);
	VEC_FREE(&pc->elements);
}

static void skipline(char** s) {
	//printf("skipping: %.10s\n", *s);
	while(**s && **s != '\n') (*s)++;
	(*s)++;
}

static char* dupname(char** s) {
	char* n;
	char* end = strpbrk(*s, " \n\r");
	n = strndup(*s, end - *s);

	*s = end + 1;

	return n;
}

int propSize[] = {
	[PT_FLOAT] = 4,
	[PT_DOUBLE] = 8,
	[PT_INT8] = 1,
	[PT_INT16] = 2,
	[PT_INT32] = 4,
	[PT_INT64] = 8,
	[PT_UINT8] = 1,
	[PT_UINT16] = 2,
	[PT_UINT32] = 4,
	[PT_UINT64] = 8,
};

struct { enum PropType type; char* name; } propTable[] = {
	// standard names
	{PT_LIST, "list"},

	{PT_FLOAT, "float"},
	{PT_DOUBLE, "double"},
	{PT_INT8, "char"},
	{PT_INT16, "short"},
	{PT_INT32, "int"},
	{PT_INT64, "long"},
	{PT_UINT8, "uchar"},
	{PT_UINT16, "ushort"},
	{PT_UINT32, "uint"},
	{PT_UINT64, "ulong"},

	// colmap uses this style
	{PT_FLOAT, "float32"},
	{PT_DOUBLE, "double64"},
	{PT_INT8, "int8"},
	{PT_INT16, "int16"},
	{PT_INT32, "int32"},
	{PT_INT64, "int64"},
	{PT_UINT8, "uint8"},
	{PT_UINT16, "uint16"},
	{PT_UINT32, "uint32"},
	{PT_UINT64, "uint64"},
};

static enum PropType parsePropType(char** s) {
	int i;
	int len = sizeof(propTable) / sizeof(propTable[0]);

	for(i = 0; i < len; i++) {
		if(!checkstr(*s, propTable[i].name)) continue;
		if(*(*s + strlen(propTable[i].name)) != ' ') continue;

		*s += strlen(propTable[i].name);
		return propTable[i].type;
	}

	return PT_MAX_VAL;
}

char* realPathFromSiblingPath(char* sibling, char* file) {
	char* falsePath, *realPath;

	char* fuckdirname = strdup(sibling);
	char* dir;

	dir = dirname(fuckdirname);

	falsePath = pathJoin(dir, file);

	realPath = realpath(falsePath, NULL);
	if(!realPath) {
		// handle errno
	}

	free(fuckdirname);
	free(falsePath);

	return realPath;
}

// returns true if there's more
static int parseHeader(PLYContents* pc, char** s) {

	enum PropType t, list_len_type, list_type;
	ply_elem* e;
	ply_prop* p;

	while(1) {

		if(checkstr(*s, "comment")) {
			*s += strlen("comment") + 1;
			char* save_ss = *s;
			//printf("comment line: '%.20s'\n", *s);
			if(checkstrcase(*s, "texturefile")) {
				*s += strlen("texturefile") + 1;
				char* ts = dupname(s);
				pc->texPath = realPathFromSiblingPath(pc->path, ts);
				free(ts);
			}
			*s = save_ss;

			skipline(s);
		}
		else if(checkstr(*s, "obj_info")) {
			(*s)++;

			skipline(s);
		}
		else if(checkstr(*s, "format")) {
			*s += strlen("format") + 1;

			if(checkstr(*s, "ascii")) {
				pc->isBinary = 0;
				pc->isBigEndian = 0;
			}
			else if(checkstr(*s, "binary_little_endian")) {
				pc->isBinary = 1;
				pc->isBigEndian = 0;
			}
			else if(checkstr(*s, "binary_big_endian")) {
				pc->isBinary = 1;
				pc->isBigEndian = 1;

				fprintf(stderr, "WARNING: big-endian ply files are not support. please upgrade your files to the 21st century.\n");
			}
			skipline(s);
		//	printf("%.10s", *s);
		}
		else if(checkstr(*s, "element")) {
			// 'element' lines contain how many are in the file and start the property list
			(*s) += strlen("element") + 1;
			VEC_INC(&pc->elements);
			e = &VEC_TAIL(&pc->elements);
			VEC_INIT(&e->props);

			e->name = dupname(s);
			e->count = strtol(*s, NULL, 10);

			if(checkstr(e->name, "vertex")) {
				pc->numVertices = e->count;
				pc->foundVertices = 1;
			}
			else if(checkstr(e->name, "face")) {
				pc->numFaces = e->count;
				pc->foundFaces = 1;
			}

			e->stride = 0;

			skipline(s);
		}
		else if(checkstr(*s, "property")) {
			VEC_INC(&e->props);
			p = &VEC_TAIL(&e->props);

			(*s) += strlen("property") + 1;

			p->type = t = parsePropType(s);
			(*s)++;

			if(t != PT_LIST) {
				p->name = dupname(s);
				printf("pname: %.20s\n", p->name);
				e->stride += propSize[t];
			}
			else {
				p->list_len = parsePropType(s);
				(*s)++;
				p->list_item = parsePropType(s);
				(*s)++;

				p->name = dupname(s);
				e->hasLists = 1;

				// check for special names
				if(checkstr(e->name, "vertex_indices")) {
					pc->foundIndices = 1;
				}
				else if(checkstr(e->name, "texcood")) {
					pc->foundFaceTexCoords = 1;
				}
			}

		//	printf("prop %s [%d]\n", p->name, p->type);

			//skipline(s);
		}
		else if(checkstr(*s, "ply")) {
		//	printf("found ply\n");
			skipline(s);
		}
		else if(checkstr(*s, "end_header")) {
			skipline(s);

			return 0;
		}
		else if(**s == '\n' || **s == '\r' || **s == ' ' || **s == '\t') {
			skipline(s); //(*s)++;
		}
		else {
			// huh?
			fprintf(stderr, "ERROR: unknown line found in ply header: %.10s\n", *s);
			*((char*)0) = 1;
			exit(1);
			skipline(s);

		}

	}

}

int64_t readValueI(enum PropType t, char** s) {
	int64_t l = 0;

	switch(t) {
		case PT_INT8:
		case PT_UINT8:
			l = **s;
			*s += 1;
			break;

		case PT_INT16:
		case PT_UINT16:
			l = *((int16_t*)(*s));
			*s += 2;
			break;

		case PT_INT32:
		case PT_UINT32:
			l = *((int32_t*)(*s));
			*s += 4;
			break;

		case PT_INT64:
		case PT_UINT64:
			l = *((int64_t*)(*s));
			*s += 8;
			break;

		case PT_FLOAT: // wtf?
			l = *((float*)(*s));
			*s += 4;
			break;

		case PT_DOUBLE: // wtf?
			l = *((double*)(*s));
			*s += 8;
			break;
	}

	return l;
}

PLYContents* PLYContents_loadPath(char* path) {
	size_t len;
	char* contents, *s;
	PLYContents* pc;
	int i, j;

	contents = readFile(path, &len);
	s = contents;

	pc = allocPLYContents();
	pc->path = path;

	// grab data in the order specified
	if(parseHeader(pc, &s)) {
		fprintf(stderr, "PLY header parsing failed\n");
		goto FAIL;
	}

	// print out some stats
	for(i = 0; i < VEC_LEN(&pc->elements); i++) {
		ply_elem* e = &VEC_ITEM(&pc->elements, i);
		printf("element %d: %s [%d] {%d}\n", i, e->name, e->count, VEC_LEN(&e->props));
		//printf("  --> stride: %d\n", e->stride);

		for(j = 0; j < VEC_LEN(&e->props); j++) {
			ply_prop* p = &VEC_ITEM(&e->props, j);

			printf("  prop %d: %s\n", j, p->name);
		}

	}
	printf("vertices %d\n", VEC_LEN(&pc->vertices));
	printf("faces %d\n", VEC_LEN(&pc->faces));

	// only supports LE binary files atm
	for(i = 0; i < VEC_LEN(&pc->elements); i++) {
		ply_elem* e = &VEC_ITEM(&pc->elements, i);

		if(!strcmp(e->name, "vertex")) {
			printf("-- parsing vertices\n");
			parseVertices(pc, e, &s);
		}
		else if(!strcmp(e->name, "face")) {
			printf("-- parsing faces\n");
			parseFaces(pc, e, &s);
		}
		else { // unrecognized elements are skipped
			if(!e->hasLists) {
				// fixed size element sets can be skipped with math
				s += e->stride * e->count;
			}
			else {
				// elements with lists have to be parsed individually in binary files
				for(int j = 0; j < VEC_LEN(&e->props); j++) {
					ply_prop* p = &VEC_ITEM(&e->props, j);

					if(p->type != PT_LIST) {
						s += propSize[p->type];
						continue;
					}

					int len = readValueI(p->list_len, &s);

					s += propSize[p->type] * len;
				}
			}

		}

		// temporary solution:
		//  record original vertices size (numVertices should be it)
		// store first found tex coord in texcoords
		// every new one initiates search starting from numVertices
		// if a complete vertex match is not found, a new vertex is created
	}

	printf("vertices %d\n", VEC_LEN(&pc->vertices));
	printf("faces %d\n", VEC_LEN(&pc->faces));
	free(contents);

	return pc;

FAIL:
	destroyPLYContents(pc);
	free(pc);
	free(contents);

	return NULL;
}

static double readValueF(enum PropType t, char** s) {
	double f;
	switch(t) {
		case PT_FLOAT: // wtf?
			f = *((float*)(*s));
			*s += sizeof(float);
			return f;

		case PT_DOUBLE: // wtf?
			f = *((double*)(*s));
			*s += sizeof(double);
			return f;

		default:
			fprintf(stderr, "PLY Loader: tried to read a non-float value.\n");
			return 0;
	}
}

static void parseVertices(PLYContents* pc, ply_elem* e, char** s) {
	int i, j;

	for(i = 0; i < e->count; i++) {
		Vector* t;

		VEC_INC(&pc->vertices);
		t = &VEC_TAIL(&pc->vertices);

		for(int j = 0; j < VEC_LEN(&e->props); j++) {
			float f;
			ply_prop* p = &VEC_ITEM(&e->props, j);

			if(p->name[1] == '\0') {
				f = readValueF(p->type, s);

				switch(p->name[0]) {
					case 'x': case 'X': t->x = f; break;;
					case 'y': case 'Y': t->y = f; break;;
					case 'z': case 'Z': t->z = f; break;;
				}
			}

			// TODO: handle list types
			//*s += propSize[p->type];
		}

		//printf("found vertex: [%.2f, %.2f, %.2f]\n", t->x, t->y, t->z);

	}
}

static void skipProp(ply_prop* p, char** s) {

	if(p->type != PT_LIST) {
		s += propSize[p->type];
		return;
	}

	int len = readValueI(p->list_len, s);
// 	printf("proplen: %d * %d ", len, propSize[p->list_item]);
// 		printf(" {%ul} ", (uint64_t)*s);

	*s += propSize[p->list_item] * len;
// 		printf(" {%ul} \n", (uint64_t)*s);

}

static void parseFaces(PLYContents* pc, ply_elem* e, char** s) {
	int i, j;
	short indicesIndex = -1;
	short texIndex = -1;

	// cache property indices for things we want to avoid strcmp in the inner loop
	for(j = 0; j < VEC_LEN(&e->props); j++) {
		ply_prop* p = &VEC_ITEM(&e->props, j);

		if(0 == strcmp(p->name, "vertex_indices")) { // used by openmvs
			indicesIndex = j;
		}
		else if(0 == strcmp(p->name, "texcoord")) { // used by openmvs
			texIndex = j;
		}
	}

	uint64_t oldpos, newpos;
	oldpos = *s;
	float minu = 1, maxu = 0, minv = 1, maxv = 0;

	for(i = 0; i < e->count; i++) {
		Vector* t;

		for(j = 0; j < VEC_LEN(&e->props); j++) {
			int index;
			ply_prop* p = &VEC_ITEM(&e->props, j);

			// large faces are triangulated
			if(j == indicesIndex) {
				int len = readValueI(p->list_len, s);

			//		printf(" z{%ul} ", (uint64_t)*s);
				int a = readValueI(p->list_item, s);
// 					printf(" a{%ul} ", (uint64_t)*s);
				int b = readValueI(p->list_item, s);
// 					printf(" b{%ul} ", (uint64_t)*s);

// 				printf("len: %d [%d, %d", len, a, b);

				for(int k = 2; k < len; k++) {
					int c = readValueI(p->list_item, s);
// 					printf(" {%ul} ", (uint64_t)*s);
// 					printf(", %d");
					VEC_PUSH(&pc->faces, a);
					VEC_PUSH(&pc->faces, b);
					VEC_PUSH(&pc->faces, c);

					b = c;
				}

// 				printf("]\n");
			}
			else if(j == texIndex) {
// 				printf("texcoord\n");
				// HACK TODO: fix
// 			printf(" {%ul} ", (uint64_t)*s);
				char* s_save = *s;

				int len = readValueI(p->list_len, s);
				//printf("tex len: %d\n", len);

				if(len != 6) {
					printf("unsupported texcoord length of %d found; skipping data (will ruin model)\n", len);
				}

				float fa, fb;

				// hope you have GCC...
				void checkTexBounds(float u, float v) {
					if(u > 1 || u < 0) printf("texture U coordinate out of bounds: %.7f\n", u);
					if(v > 1 || v < 0) printf("texture V coordinate out of bounds: %.7f\n", v);
				//printf("[%.9f, %.9f]\n", u, v);
					minu = fmin(u, minu);
					minv = fmin(v, minv);
					maxu = fmax(u, maxu);
					maxv = fmax(v, maxv);
				}

				// 1
				fa = readValueF(p->list_item, s);
				fb = readValueF(p->list_item, s);
			//	printf("tc: %.5f, %.5f\n", fa, fb);

				checkTexBounds(fa, fb);
				VEC_INC(&pc->texcoords);
				VEC_TAIL(&pc->texcoords).x = fa;
				VEC_TAIL(&pc->texcoords).y = fb;

				VEC_PUSH(&pc->indicesTex, VEC_LEN(&pc->texcoords) - 1);

				// 2
				fa = readValueF(p->list_item, s);
				fb = readValueF(p->list_item, s);
			//	printf("tc: %.5f, %.5f\n", fa, fb);
				checkTexBounds(fa, fb);
				VEC_INC(&pc->texcoords);
				VEC_TAIL(&pc->texcoords).x = fa;
				VEC_TAIL(&pc->texcoords).y = fb;

				VEC_PUSH(&pc->indicesTex, VEC_LEN(&pc->texcoords) - 1);

				// 3
				fa = readValueF(p->list_item, s);
				fb = readValueF(p->list_item, s);
			//	printf("tc: %.5f, %.5f\n", fa, fb);
				checkTexBounds(fa, fb);
				VEC_INC(&pc->texcoords);
				VEC_TAIL(&pc->texcoords).x = fa;
				VEC_TAIL(&pc->texcoords).y = fb;

				VEC_PUSH(&pc->indicesTex, VEC_LEN(&pc->texcoords) - 1);

				*s = s_save;
				skipProp(p, s); // printf(" {%ul} ", (uint64_t)*s);
			}
			else {
				skipProp(p, s);
			}

			newpos = *s;

// 			printf("oldpos: %ul, newpos: %ul, dist: %ul \n", oldpos, newpos, newpos - oldpos);
			oldpos = newpos;

		}

	}

}


code:
#ifndef __mesh_viewer__plyloader_h__
#define __mesh_viewer__plyloader_h__

#include "common_gl.h"
#include "common_math.h"

#include "ds.h"

enum PropType {
	PT_LIST, // 0
	PT_FLOAT,
	PT_DOUBLE,
	PT_INT8,
	PT_INT16,
	PT_INT32, // 5
	PT_INT64,
	PT_UINT8,
	PT_UINT16,
	PT_UINT32,
	PT_UINT64, // 10
	PT_MAX_VAL
};

typedef struct {
	char* name;
	enum PropType type;
	enum PropType list_len;
	enum PropType list_item;

} ply_prop;

typedef struct {
	char* name;
	long count;
	int stride; // lists don't have this

	char hasLists;

	VEC(ply_prop) props;

} ply_elem;

typedef struct {

	char* path;

		// these are read from the header
	char isBinary;
	char isBigEndian;

	long numVertices;
	long numFaces;

	VEC(char*) comments;

	char* texPath; // only one texture supported atm

	VEC(Vector) vertices;
	VEC(Vector4) colors;
	VEC(Vector) normals; // NYI
	VEC(Vector) texcoords;

	VEC(uint32_t) faces;
	VEC(uint32_t) indicesTex;

	// parsing info

	VEC(ply_elem) elements;

	// elements
	char foundVertices;
	char foundFaces;

	// properties
	char foundIndices;
	char foundNormals;
	char foundVertTexCoords;
	char foundFaceTexCoords;
	char foundVertColors;

} PLYContents;

PLYContents* PLYContents_loadPath(char* path);
PLYContents* PLYContents_load(char* contents, size_t length);

#endif // __mesh_viewer__plyloader_h__


code:
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <math.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include "ds.h"
#include "hash.h"

#include "c3dlas/c3dlas.h"

#include "utilities.h"
#include "objloader.h"
#include "shader.h"
#include "renderable.h"

Renderable* renderable_Create(GLuint type, char* shaderPath, VAOConfig* vaoOpts, void* data, int dataCnt) {

	Renderable* r;
	int stride;
	int i;
	int offset = 0;

	// allocation and init
	r = calloc(1, sizeof(*r));
	if(!r) {
		fprintf(stderr, "OOM\n");
		exit(2);
	}

	r->data = data;
	r->dataCnt = dataCnt;
	r->type = type;

	// shader
	r->prog = loadCombinedProgram(shaderPath);
	glexit("Renderable shader");
	if(!r->prog) {
		printf("Failed to load shader '%s'\n", shaderPath);
	}
	else {
		r->model_ul = glGetUniformLocation(r->prog->id, "mModel");
		r->view_ul = glGetUniformLocation(r->prog->id, "mView");
		r->proj_ul = glGetUniformLocation(r->prog->id, "mProj");
		r->tex_ul = glGetUniformLocation(r->prog->id, "tex");
		glexit("Renderable matrix uniforms");
	}

	// vertex data
	r->vao = makeVAO(vaoOpts);
	glexit("Renderable VAO");

	glBindVertexArray(r->vao);

	glGenBuffers(1, &r->vbo);
	glBindBuffer(GL_ARRAY_BUFFER, r->vbo);

	stride = getVAOStride(vaoOpts);

	for(i = 0; vaoOpts[i].sz != 0; i++) {
		glEnableVertexAttribArray(i);
		glVertexAttribPointer(i, vaoOpts[i].sz, vaoOpts[i].type, GL_FALSE, stride, offset);
		glexit("Renderable vertex attribs");

		offset += getVAOItemSize(&vaoOpts[i]);
	}
	printf("stride: %d\n", stride);
	glBufferData(GL_ARRAY_BUFFER, dataCnt * stride, data, GL_STATIC_DRAW);
	glexit("Renderable buffer data");

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
	glexit("static mesh vbo load");

	return r;
}

void Renderable_Destroy(Renderable* r) {
	if(r->data) free(r->data);
	if(r->texturePath) free(r->texturePath);
	if(r->shaderPath) free(r->shaderPath);

	if(r->tex) Texture_Release(r->tex);
	if(r->prog) ShaderProgram_Release(r->prog);
};

void Renderable_Free(Renderable* r) {
	if(!r) return;
	Renderable_Destroy(r);
	free(r);
};

void renderable_Draw(Renderable* r, Matrix* view, Matrix* proj) {

	Matrix model;

	if(r->scale == 0) {
		printf("Renderable with zero scale. Fixing.\n");
		r->scale = 1;
	}

	//mFastMul(view, proj, &mvp);
	mIdent(&model);
	mScale3f(r->scale, r->scale, r->scale, &model);
	mTransv(&r->pos, &model);

	glUseProgram(r->prog->id);

	if(r->tex) {
		glActiveTexture(GL_TEXTURE0 + 5);
		glBindTexture(GL_TEXTURE_2D, r->tex->tex_id);

		glUniform1i(r->tex_ul, 5);
	}

	glUniform1i(r->tex_ul, 5);

	glUniformMatrix4fv(r->model_ul, 1, GL_FALSE, &model.m);
	glUniformMatrix4fv(r->view_ul, 1, GL_FALSE, &view->m);
	glUniformMatrix4fv(r->proj_ul, 1, GL_FALSE, &proj->m);
// 	glUniform3f(color_ul, .5, .2, .9);

	glBindVertexArray(r->vao);
	glBindBuffer(GL_ARRAY_BUFFER, r->vbo);
	glexit("Renderable vbo");

	//printf("vertices %d\n", r->dataCnt);
	glDrawArrays(r->type, 0, r->dataCnt);
	glexit("Renderable draw");

}

int Renderable_applyTexturePath(Renderable* r, char* path) {

	r->tex = loadBitmapTexture(path);
	if(!r->tex) return 1;

	r->texturePath = path;

	return 0;
}

struct RenderableOBJVertex {
	Vector v, n;
	struct {
		float u, v;
	} t;
};

Renderable* renderable_FromPLY(PLYContents* pc) {
	int i;
	Renderable* r;
	VAOConfig opts[] = {
		// per vertex
		{3, GL_FLOAT}, // position
		{3, GL_FLOAT}, // normal
		{2, GL_FLOAT}, // tex

		{0, 0}
	};

	Vector null = {0,0,0};

	//return NULL;

	struct RenderableOBJVertex* vertices = calloc(1, VEC_LEN(&pc->faces) * sizeof(*vertices));

	for(i = 0; i < VEC_LEN(&pc->faces); i++) {
		int q = VEC_ITEM(&pc->faces, i);
		int r = VEC_ITEM(&pc->indicesTex, i);

		if(q >= VEC_LEN(&pc->vertices)) {
			printf("vertex index outside bounds %d: (%d, %d)\n", i, q, VEC_LEN(&pc->vertices));
			continue;
		}

		vCopy(&VEC_ITEM(&pc->vertices, q), &vertices[i].v);
		vertices[i].t.u = VEC_ITEM(&pc->texcoords, r).x;
		vertices[i].t.v = VEC_ITEM(&pc->texcoords, r).y;

		vCopy(&null, &vertices[i].n);

		//if(vertices[i].v.x != 0.0)
		//printf("[%.8f, %.8f, %.8f]\n", vertices[i].v.x, vertices[i].v.y, vertices[i].v.z);

		//vertices[i].t.u = obj->faces[i].t.x * 65536;
		//vertices[i].t.v = obj->faces[i].t.y * 65536;

		//printf("texcoord: [%f, %f] -> [%d, %d]\n",obj->faces[i].t.x, obj->faces[i].t.y, vertices[i].t.u,vertices[i].t.v );
	}

	r = renderable_Create(
		GL_TRIANGLES,
		"staticMesh",
		opts,
		vertices,
		VEC_LEN(&pc->faces));

	printf("tex path: '%s'\n", pc->texPath);
	if(pc->texPath) Renderable_applyTexturePath(r, pc->texPath);

	return r;
}

Renderable* renderable_FromOBJ(OBJContents* obj) {
	int i;
	VAOConfig opts[] = {
		// per vertex
		{3, GL_FLOAT}, // position
		{3, GL_FLOAT}, // normal
		{2, GL_UNSIGNED_SHORT, GL_TRUE}, // tex

		{0, 0}
	};

	int vertexCnt = 3 * obj->faceCnt;

	struct RenderableOBJVertex* vertices = calloc(1, vertexCnt * sizeof(*vertices));

	for(i = 0; i < vertexCnt; i++) {
		vCopy(&obj->faces[i].v, &vertices[i].v);
		vCopy(&obj->faces[i].n, &vertices[i].n);

		vertices[i].t.u = obj->faces[i].t.x * 65536;
		vertices[i].t.v = obj->faces[i].t.y * 65536;

		//printf("texcoord: [%f, %f] -> [%d, %d]\n",obj->faces[i].t.x, obj->faces[i].t.y, vertices[i].t.u,vertices[i].t.v );

	}

	return renderable_Create(
		GL_TRIANGLES,
		"staticMesh",
		opts,
		vertices,
		3 * obj->faceCnt);

}


code:
#ifndef __EACSMB_RENDERABLE_H__
#define __EACSMB_RENDERABLE_H__

#include "ds.h"

#include "texture.h"
#include "plyloader.h"

typedef struct RenderableVertex {
	Vector v, n;
	struct {
		unsigned short u, v;
	} t;
} RenderableVertex;

typedef struct Renderable {

	// old stuff
	GLenum type; // eg, GL_TRIANGLES
	void* data;
	int dataCnt;

	GLuint vbo;
	GLuint vao;

	char* texturePath;
	Texture* tex;
	GLuint tex_ul;

	char* shaderPath;
	ShaderProgram* prog;
	GLuint model_ul, view_ul, proj_ul;

	GLuint texID;

	Vector pos;
	Vector raxis;
	float rtheta;
	float scale;

	Matrix composed;

} Renderable;

Renderable* renderable_FromOBJ(OBJContents* obj);
void renderable_Draw(Renderable* r, Matrix* view, Matrix* proj);
Renderable* renderable_Create(GLuint type, char* shaderPath, VAOConfig* vaoOpts, void* data, int dataCnt);
void Renderable_Destroy(Renderable* r);
void Renderable_Free(Renderable* r);

#endif // __EACSMB_RENDERABLE_H__


code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#include <X11/X.h>
#include <X11/Xlib.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include "c3dlas/c3dlas.h"
#include "c3dlas/meshgen.h"
#include "text/text.h"
#include "c_json/json.h"
#include "json_gl.h"

#include "utilities.h"
#include "config.h"
#include "shader.h"
#include "window.h"
#include "game.h"

/*

normals on a mesh

bounding box rendering

axes rendering

adjacency list

line strip rendering
point list rendering

math functions for various views
	left/right, up vector, look at point from distance

*/

void loadSettings(char* path, GameState* gs) {

	json_file_t* jsf = json_load_path("assets/config/settings.json");

	json_value_t* tex;
	json_obj_get_key(jsf->root, "kb", &tex);

	json_obj_unpack_struct(tex,
		JSON_UNPACK(&gs->uSettings, keyRotateSensitivity, JSON_TYPE_FLOAT),
		JSON_UNPACK(&gs->uSettings, keyScrollSensitivity, JSON_TYPE_FLOAT),
		JSON_UNPACK(&gs->uSettings, keyZoomSensitivity, JSON_TYPE_FLOAT),

		JSON_UNPACK(&gs->uSettings, mouseRotateSensitivity, JSON_TYPE_FLOAT),
		JSON_UNPACK(&gs->uSettings, mouseScrollSensitivity, JSON_TYPE_FLOAT),
		JSON_UNPACK(&gs->uSettings, mouseZoomSensitivity, JSON_TYPE_FLOAT),
		NULL
	);

}


code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include <libgen.h>

#include "utilities.h"
#include "shader.h"

const int BUFFER_SZ = 1024 * 8;
const char* SHADER_BASE_PATH = "./src/shaders/";

struct ShaderBuf {
	char** buffers;
	int allocSz;
	int count;

};

void ShaderProgram_Release(ShaderProgram* t) {
	printf("NYI: ShaderProgram_Release(), %s:%d\n", __FILE__, __LINE__);

}

void printLogOnFail(id) {

	GLint success, logSize;
	GLsizei len;
	GLchar* log;

	if(!glIsShader(id)) {
		fprintf(stderr, "id is not a shader!\n");
		return;
	}

	glGetShaderiv(id, GL_COMPILE_STATUS, &success);
	if(success) return;

	glGetShaderiv(id, GL_INFO_LOG_LENGTH, &logSize);
	log = (GLchar*)malloc(logSize);

	glGetShaderInfoLog(id, logSize, &len, log);
	fprintf(stderr, "Shader Log:\n%s", (char*)log);

	free(log);
}

void printProgLogOnFail(id) {

	GLint success, logSize;
	GLsizei len;
	GLchar* log;

	if(!glIsProgram(id)) {
		fprintf(stderr, "id is not a program!\n");
		return;
	}

	glGetProgramiv(id, GL_LINK_STATUS, &success);
	if(success) return;

	glGetProgramiv(id, GL_INFO_LOG_LENGTH, &logSize);
	log = (GLchar*)malloc(logSize);

	glGetProgramInfoLog(id, logSize, &len, log);
	fprintf(stderr, "Program Log:\n%s", (char*)log);

	free(log);
}

GLuint loadShaderSource(char* source, int length, GLenum type) {

	GLuint id;
	glerr("pre shader create error");
	id = glCreateShader(type);
	glerr("shader create error");

	glShaderSource(id, 1, (const char**)&source, &length);
	glerr("shader source error");

	glCompileShader(id);
	printLogOnFail(id);
	glerr("shader compile error");

	return id;
}

void deleteShader(Shader* s) {

}

GLenum indexToEnum(int index) {
	GLenum a[] = {
		GL_VERTEX_SHADER,
		GL_TESS_CONTROL_SHADER,
		GL_TESS_EVALUATION_SHADER,
		GL_GEOMETRY_SHADER,
		GL_FRAGMENT_SHADER,
		GL_COMPUTE_SHADER
	};

	if(index < 0 || index > 5) return -1;

	return a[index];
}

int nameToIndex(char* name) {

	if(0 == strcasecmp(name, "VERTEX")) return 0;
	if(0 == strcasecmp(name, "TESS_CONTROL")) return 1;
	if(0 == strcasecmp(name, "TESS_EVALUATION")) return 2;
	if(0 == strcasecmp(name, "GEOMETRY")) return 3;
	if(0 == strcasecmp(name, "FRAGMENT")) return 4;
	if(0 == strcasecmp(name, "COMPUTE")) return 5;

	return -1;
}

GLenum nameToEnum(char* name) {

	if(0 == strcasecmp(name, "VERTEX")) return GL_VERTEX_SHADER;
	if(0 == strcasecmp(name, "TESS_CONTROL")) return GL_TESS_CONTROL_SHADER;
	if(0 == strcasecmp(name, "TESS_EVALUATION")) return GL_TESS_EVALUATION_SHADER;
	if(0 == strcasecmp(name, "GEOMETRY")) return GL_GEOMETRY_SHADER;
	if(0 == strcasecmp(name, "FRAGMENT")) return GL_FRAGMENT_SHADER;
	if(0 == strcasecmp(name, "COMPUTE")) return GL_COMPUTE_SHADER;

	return -1;
}

int extractShader(char** source, GLuint progID) {

	char* base, *end;
	int cnt;
	char typeName[24];
	GLenum type;
	GLuint id;

	base = strstr(*source, "\n#shader");
	if(base == NULL) {
		return 1; // no shaders left
	}

	cnt = sscanf(base + 8, " %23s", typeName); // != 1 for failure
	if(cnt == EOF || cnt == 0) {
		return 2; //
	}

	// skip #shader line
	base = strchr(base + 1, '\n');

	end = strstr(base, "\n#shader");
	if(end == NULL) {
		end = *source + strlen(*source);
	}

	printf(" %s", typeName);

	*source = end;

	type = nameToEnum(typeName);
	if(type == -1) {
		fprintf(stderr, "Invalid shader type %s\n", typeName);
		return 3;
	}

	id = loadShaderSource(base, end - base, type);
	if(!id) { // BUG: look up the real failure code
		return 4;
	}

	glAttachShader(progID, id);
	glerr("Could not attach shader");

	return 0;
}

struct sourceFile;

// a piece of a file, between others
struct sourceFragment {
	int srcLen; // if -1, it's a sourceFile struct
	char* src;

	//char* basePath;
	char* filename;

	int srcStartingLine; // where the first line of this fragment is in the original source file
	int shaderStartingLine; // where the first line of this fragment falls in the final concatenated source
	int lineCount; // number of lines in this fragment
	int shaderType;

	struct sourceFragment* next;
	struct sourceFragment* prev;
};
/* deprecated in favor of just a list of fragments
struct sourceFile {
	char* basePath;
	char* filename;
	int startingLine; // where the first line of this file falls in the final concatenated source
	int endingLine;

	struct sourceFragment* frags;
}
*/

int scanShaderType(const char* s) {
	int cnt, type;

	char typeName[34];

	cnt = sscanf(s, " %23s", typeName); // != 1 for failure
	if(cnt == EOF || cnt == 0) {
		printf("unrecognized shader type \n");
		return -1;
	}

	printf(" %s", typeName);

	type = nameToIndex(typeName);
	if(type == -1) {
		fprintf(stderr, "Invalid shader type %s\n", typeName);
		return -1;
	}

	return type;
}

int newTopLevel(char* file) {

	struct sourceFragment* sf;

	sf = preloadShader(".", file);

	//preloadShader(sf);

}

static struct sourceFragment* nibble(char** source);
static struct sourceFragment* nibbleFile(char** source, char* basePath);

struct sourceFragment* preloadFile(char* basePath, char* filename) {
	struct sourceFragment* frag;
	struct sourceFragment* head, *tail;
	char* source, *base, *end, *s;
	int srcLen;

	char typeName[24];

	char* truebase;
	char* fullpath;

	fullpath = pathJoin(basePath, filename);
	printf("preloading file '%s'\n", fullpath);

	// TODO: basename shit

	source = readFile(fullpath, &srcLen);
	if(!source) {
		// TODO copypasta, fix this
		return NULL;
	}

// 	sf = calloc(1, sizeof(struct sourceFragment));
//
// 	sf->basePath = basePath;
// 	sf->filename = filename;

	truebase = strdup(dirname(fullpath));

	s = source;
	base = source;
	//tail = sf;

	struct sourceFragment fake;
	fake.next = NULL;
	fake.prev = NULL;

	tail = &fake;
	head = &fake;

	int i = 0;

	while(*s && i++ < 100) {

		printf("~~~~~~~~~~~~~~~\n");
		s = strstr(s, "\n#");
		if(!s) {
			printf("nothing left\n");
			break;
		}

		s += 2;

		printf("%.10s\n", s);
		if(0 == strncmp(s, "include", strlen("include"))) {

			printf("nibbling file\n\n");

			// nibble the part before this
			frag = calloc(1, sizeof(struct sourceFragment));
			tail->next = frag;
			frag->prev = tail;
			frag->shaderType = -1;

			frag->srcLen =  s - base - 2;
			frag->src = strndup(base, frag->srcLen);
			frag->filename = fullpath;

			tail = frag;

			frag = nibbleFile(&s, truebase);
			if(!frag) {
				printf("preload file failed\n");
				return NULL; // TODO: memory leak
			}

			if(!head) head = frag;

			if(tail) {
				tail->next = frag;
				frag->prev = tail;
			}
			else {
				tail = frag;
			}

			// advance tail to the end. preloadFile can return a list.
			while(tail->next) tail = tail->next;

			// bove the base pointer up
			base = s;
		}
		else if(0 == strncmp(s, "shader", strlen("shader"))) {
			// keep going for now
			//base = s;

			// nibble the part before this
			frag = calloc(1, sizeof(struct sourceFragment));
			tail->next = frag;
			frag->prev = tail;
			frag->shaderType = -1;

			frag->srcLen =  s - base - 2;
			printf("before part len = %d\n", frag->srcLen);
			frag->src = strndup(base, frag->srcLen);
			printf("duipped len = %d\n", strlen(frag->src));
			frag->filename = fullpath;

			tail = frag;

			// add the shader fragment
			frag = calloc(1, sizeof(struct sourceFragment));
			tail->next = frag;
			frag->prev = tail;

			frag->src = strlndup(s - 1);
			printf("after duipped len = %d\n", strlen(frag->src));
			frag->srcLen =  strlen(frag->src);
			frag->filename = fullpath;

			tail = frag;

			// skip to end of line
			base = strchr(s, '\n');

			frag->shaderType = scanShaderType(s + 6);

			printf("nibbling shader\n");
		}
		else { // some other directive
			// keep going
			//base = s;
			printf("nibbling else\n");
		}
	}

	printf("%d %d\n", base, s);
	int n = strlen(base);
	if(n) {
		printf("extra left\n\n");
		// add the fragment
		frag = calloc(1, sizeof(struct sourceFragment));
		tail->next = frag;
		frag->prev = tail;
		frag->shaderType = -1;

		frag->srcLen =  n;
		frag->src = strndup(base, frag->srcLen);
		frag->filename = fullpath;
	}
	else {

		printf("no extra left\n\n");
	}

	// walk the list and fill in line numbers

	return head->next;
}

static struct sourceFragment* nibbleFile(char** source, char* basePath) {
	struct sourceFragment* frag, *fileFrag;
	int cnt;

	char includeName[256];
	printf("%.10s\n", (*source)+7);
	// extract the file name

	char* s = *source + 7;

	//cnt = sscanf((*source) + 7, " \"%255s\"", includeName); // != 1 for failure

	char* a = strchr(s, '"');
	char* b = strchr(a + 1, '"');
	char* c = strndup(a + 1, b - a - 1);

	printf("duped string '%s'\n", c);

	//if(cnt == EOF || cnt == 0) {
	//	fprintf(stderr, "Could not parse included file name\n");
	//	return NULL; // invalid parse
	//}

	printf("including file %s\n", c);
	// TODO: basename magic here
	//return NULL;
	return preloadFile(basePath, c);
}

Shader* preloadShader(char* basePath, char* filename) {
	struct sourceFragment* frag, *x, *y;
	int lines;

	struct sourceFragment* sfrags[6] = {0, 0, 0, 0, 0, 0};
	short slen[6] = {0, 0, 0, 0, 0, 0};
	char** schars[6] = {0, 0, 0, 0, 0, 0};

	frag = preloadFile(basePath, filename);
	if(!frag) return NULL;

	// fill in line numbers
	lines = 0;
	x = frag;
	while(x) {
		int n;

		n = strlinecnt(x->src);

		x->srcStartingLine = lines;
		x->lineCount = n;

		lines += n;

		x = x->next;
	}

	// extract shaders

	int prevType = -1;
	x = frag;
	while(x) {
		int n;
		printf("looping prevtype=%d n=%d x->sT=%d \n", prevType, n, x->shaderType);
		printf("  %.5s\n", x->src);
		if(x->shaderType == prevType || x->shaderType < 0) {
			if(prevType != -1) slen[prevType]++;
			x = x->next;
			continue;
		}

		// break the chain here

		printf("^^ breaking chain\n");
		// TODO: null deref checks
		x->prev->next = NULL;
		x->prev = NULL;

		// save the head
		// should filter out the "shader" ones
		if(sfrags[x->shaderType]) {
			fprintf(stderr, "shader already found %d\n", x->shaderType);

			return NULL; // TODO: error handling
		}

		sfrags[x->shaderType] = x;
		prevType = x->shaderType;

		// keep count along the way
		slen[x->shaderType]++;

		x = x->next;
	}

	int i, n, z;
	// make char* arrays for opengl
	for(i = 0; i < 6; i ++) {
		printf(" i = %d \n", i);
		printf(" slen = %d \n", slen[i]);
		// allocate an array of char*'s twice as big as it needs to be
		// the extra pointer is for the #line directives
		schars[i] = malloc(slen[i] * sizeof(char*) * 2);

		x = sfrags[i];
		n = 0;
		int linecnt = 1;
		while(x) {
			printf("%d, %.20s\n", n, x->src);
			// TODO: prepend the line strings

			z = snprintf(NULL, 0, "#line %d %d \n", linecnt, i+1);
			printf("z = %d\n", z);
			schars[i][n] = malloc(z * sizeof(char)+1);
			snprintf(schars[i][n], z+1, "#line %d %d \n", linecnt, i+1);
			printf("#line %d %d\n", linecnt, i+1);

			n++;

			if(0 != strncmp(x->src, "#shader", strlen("#shader")))
				schars[i][n++] = x->src;

			linecnt += x->lineCount;

			x = x->next;
		}

	}

	// print out the strings as debug info
	for(i = 0; i < 6; i ++) {

		if(slen[i] == 0) continue;

		printf("-%d-----------------------------------------", i);
// 		type = nameToEnum(typeName);
// 		if(type == -1) {
// 			fprintf(stderr, "Invalid shader type %s\n", typeName);
// 			return 3;
// 		}

		x = sfrags[i];
		n = 0;
		int linecnt = 1;
		if(i == 0) {
		while(x) {
			printf("========\n");
			printf(x->src);

			x = x->next;
		}
		}

		GLuint id;
		glerr("pre shader create error");
		id = glCreateShader(indexToEnum(i));
		glerr("shader create error");

		glShaderSource(id, slen[i], schars[i], NULL);
		glerr("shader source error");

		printf("compiling\n");
		glCompileShader(id);
		printLogOnFail(id);
		glerr("shader compile error");

		//	glAttachShader(0, id);
	glerr("Could not attach shader");

	//	id = loadShaderSource(base, end - base, type);
	//	if(!id) { // BUG: look up the real failure code
	//		return 4;
	//	}

		//schars[i] = malloc(slen[i] * sizeof(char*));

		//printf("%d, %.20s\n", i, schars[i]);

	}
}

ShaderProgram* loadCombinedProgram(char* path) {

	ShaderProgram* prog;
	char* source, *spath, *end, *base;
	int srcLen;
	char typeName[24];
	GLenum type;
	GLuint id;

	int bplen = strlen(SHADER_BASE_PATH);

	prog = calloc(1, sizeof(ShaderProgram));
	glerr("pre shader create 1");
	prog->id = glCreateProgram();

	// grab the source
	spath = (char*)malloc(bplen + strlen(path) + 6);
	sprintf(spath, "%s%s.glsl", SHADER_BASE_PATH, path);

	source = readFile(spath, &srcLen);
	if(!source) {
		free(spath);
		return NULL;
	}

	//parse out the contents one shader at a time

	base = source;
	int c = 0;

	printf("Loading %s:\n   ", spath);
	while(!extractShader(&base, prog->id)) glexit("");
	printf("\n");

	free(source);
	free(spath);

	glLinkProgram(prog->id);
	printProgLogOnFail(prog->id);
	glerr("linking program");

	return prog;
}


code:
#ifndef __EACSMB_SHADER_H__
#define __EACSMB_SHADER_H__

typedef struct {
	char* name;
	GLuint id;

} Shader;

typedef struct {
	Shader* tcs;
	Shader* ts;
	Shader* vs;
	Shader* gs;
	Shader* fs;
	GLuint id;

} ShaderProgram;

Shader* loadShader(char* path, GLenum type);
ShaderProgram* loadProgram(char* vname, char* fname, char* gname, char* tcname, char* tname);

ShaderProgram* loadCombinedProgram(char* path);

struct sourceFragment* preloadFile(char* basePath, char* filename);
Shader* preloadShader(char* basePath, char* filename);
int newTopLevel(char* file);

void ShaderProgram_Release(ShaderProgram* t);

#endif // __EACSMB_SHADER_H__


code:
#shader VERTEX

#version 330 core

layout (location = 0) in vec3 pos_in;
layout (location = 1) in vec4 color_in;

uniform mat4 mModel;
uniform mat4 mView;
uniform mat4 mProj;

// out vec4 vs_pos;
out vec4 vs_color;

void main() {
	gl_Position = (mProj * mView * mModel) * vec4(pos_in, 1);
	vs_color = color_in;
}

#shader FRAGMENT

#version 330

in vec4 vs_color;

layout(location = 0) out vec4 out_Color;
layout(location = 1) out vec4 out_Normal;
//layout(location = 2) out vec4 out_Lighting;

void main(void) {

// 	out_Color = vec4(vs_color.xyz, 1); //vs_norm;
	out_Color = vec4(vs_color.xyz / 256, 1); //vs_norm;
	//out_Color = vec4(tc.x, 0, 0, 1); //vs_norm;
	out_Normal = normalize(vec4(0.5, 0.5, 0.5, 1)); //vs_norm;
}


code:
#shader VERTEX

#version 330 core

layout (location = 0) in vec3 pos_in;
layout (location = 1) in vec2 tex_in;
layout (location = 2) in vec4 color_in;

uniform mat4 mProj;
uniform mat4 mModel;

out vec2 texCoord;
out vec4 color;

void main() {
// 	gl_Position = (mProj * mView * mModel) * vec4(pos_in.xyz, 1.0);

	texCoord = tex_in;
	color = color_in;
	gl_Position = (mProj * mModel) * vec4(pos_in.xyz, 1.0);
}

#shader FRAGMENT

#version 330

// fragment shader
uniform sampler2D fontTex;
uniform float smoothLower;
uniform float smoothUpper;
uniform float smoothLimit;

in vec2 texCoord;
in vec4 color;

void main(void) {

	float d = texture2D(fontTex, texCoord).r;

	float a;

	d = 1 - d;

	a = smoothstep(smoothLower, smoothUpper, abs(d));

	if(a < smoothLimit) discard;

	gl_FragColor = vec4(color.abg, a);

}


code:
#shader VERTEX

#version 330

layout (location = 0) in vec3 pos;

void main() {
	gl_Position = vec4(pos, 1.0);
}

#shader FRAGMENT

#version 330

uniform sampler2D sDiffuse;
uniform sampler2D sNormals;
uniform sampler2D sDepth;
uniform sampler2D sSelection;
uniform sampler2D sLighting;

uniform int debugMode;
uniform vec2 clipPlanes;

uniform vec3 sunNormal;

// forward matrices
uniform mat4 mWorldView;
uniform mat4 mViewProj;

// inverse matrices
uniform mat4 mViewWorld;
uniform mat4 mProjView;

uniform vec2 resolution;

out vec4 FragColor;

void main() {
	vec2 tex = gl_FragCoord.xy / resolution.xy;
	vec3 normal = texture(sNormals, tex).xyz;

	if(debugMode == 0) {
		// normal rendering

		// reconstruct world coordinates
		vec2 screenCoord = gl_FragCoord.xy / resolution.xy;

		float depth = texture(sDepth, screenCoord).r;
		if (depth > 0.99999) {
			discard; // probably shouldn't be here
		}

		float ndc_depth = depth * 2.0 - 1.0;

		mat4 invVP = inverse(mViewProj * mWorldView);

		vec4 tmppos = invVP * vec4(screenCoord * 2.0 - 1.0, ndc_depth, 1.0);
		vec3 pos = tmppos.xyz / tmppos.w;
		// pos is in world coordinates now

		vec3 viewpos = (inverse(mViewProj * mWorldView) * vec4(0,0,0,1)).xyz;

		vec3 viewdir = normalize(viewpos - pos);
		vec3 sundir = normalize(vec3(3,3,20));

		float lambertian = max(dot(sundir, normal), 0.0);

		vec3 halfDir = normalize(sundir + viewdir);
		float specAngle = max(dot(halfDir, normal), 0.0);
		float specular = pow(specAngle, 16);

		vec3 diffuseColor = texture(sDiffuse, tex).rgb;
		vec3 specColor = vec3(0,0,0);//normalize(vec3(1,1,1));

		vec3 ambient = vec3(0.1,0.1,0.1);
		FragColor = vec4(ambient + lambertian * diffuseColor+ specular * specColor, 1.0);

	}
	else if(debugMode == 1) {
		// diffuse
		FragColor = vec4(texture(sDiffuse, tex).rgb,  1.0);
	}
	else if(debugMode == 2) {
		// normals
		FragColor = vec4(texture(sNormals, tex).rgb + vec3(.5,0,.1),  1.0);
	}
	else if(debugMode == 3) {
		// depth
		FragColor = vec4(texture(sDepth, tex).rrr,  1.0);
	}
	else if(debugMode == 4) {
		// selection buffer
		FragColor = vec4(texture(sSelection, tex).rgb,  1.0);
	}
	else if(debugMode == 5) {
		// lighting buffer
		FragColor = vec4(texture(sLighting, tex).rgb,  1.0);
	}

//	FragColor = vec4(texture(sNormals, tex).rgb,  1.0);
}


code:
#shader VERTEX

#version 330 core

layout (location = 0) in vec3 pos_in;
layout (location = 1) in vec3 norm_in;
layout (location = 2) in vec2 tex_in;

uniform mat4 mModel;
uniform mat4 mView;
uniform mat4 mProj;

// out vec4 vs_pos;
out vec4 vs_norm;
out vec2 vs_tex;

void main() {
	gl_Position = (mProj * mView * mModel) * vec4(pos_in, 1);
	vs_norm = vec4(norm_in, 1); // normalize(vec4(1,1,1,0));
	vs_tex = tex_in;
}

#shader FRAGMENT

#version 330

// in vec4 vs_pos;
in vec4 vs_norm;
in vec2 vs_tex;

// fragment shader
uniform vec4 color;
uniform sampler2D tex;

layout(location = 0) out vec4 out_Color;
layout(location = 1) out vec4 out_Normal;
//layout(location = 2) out vec4 out_Lighting;

void main(void) {
	vec2 tc = vs_tex.xy;

	out_Color = vec4(texture(tex, vec2(tc.x, 1-tc.y)).xyz, 1); //vs_norm;
	//out_Color = vec4(tc.xy, 1, 1); //vs_norm;
	out_Normal = normalize(vec4(0.5, 0.5, 0.5, 1)); //vs_norm;
//	out_Lighting = vec4(0.5, 0.5, 1, 1);
}


code:
#shader VERTEX

#version 330 core

layout (location = 0) in vec3 pos_in;
layout (location = 1) in vec2 tex_in;
layout (location = 2) in vec4 color_in;

uniform mat4 mProj;
uniform mat4 mModel;

out vec2 texCoord;
out vec4 color;

void main() {
// 	gl_Position = (mProj * mView * mModel) * vec4(pos_in.xyz, 1.0);

	texCoord = tex_in;
	color = color_in;
	gl_Position = (mProj * mModel) * vec4(pos_in.xyz, 1.0);
}

#shader FRAGMENT

#version 330

// fragment shader
uniform sampler2D fontTex;

in vec2 texCoord;
in vec4 color;

void main(void) {

	float d = texture2D(fontTex, texCoord).r;

	float a;

	d = 1 - d;

	a = smoothstep(0.6, .8, abs(d));

	if(a < 0.1) discard;

	gl_FragColor = vec4(color.abg, a);

}


code:
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <math.h>
#include <float.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include "c3dlas/c3dlas.h"

#include "utilities.h"
#include "objloader.h"
#include "shader.h"
#include "staticMesh.h"

int static parseFaceVertex(char** s, int* info);

struct SolidVertex {
	Vector pos;
	Vector face_normal;
	struct {
		float u, v;
	} texcoords;
};

static HASH_FN_FOR_TYPE(vector_hash_fn, Vector)

float StaticMesh_setScale(StaticMesh* sm, float newscale) {

	sm->scale = newscale;

	sm->bbox.rend->scale = newscale;
	sm->bbox.rendAxes->scale = newscale;
	sm->solid->scale = newscale;
	sm->wireframe->scale = newscale;
	sm->points->scale = newscale;

	return newscale;
}

static void generateSolid(StaticMesh* sm) {

	int i, face_cnt;

	struct SolidVertex* faces;

	face_cnt = sm->faceCnt = VEC_LEN(&sm->vertexIndices) / 3;

	faces = malloc(face_cnt * 3 * sizeof(*faces));
	CHECK_OOM(faces);

	// fill in the faces, calculate face normals
	for(i = 0; i < VEC_LEN(&sm->vertexIndices); i += 3) {
		Vector* a, *b, *c;
		Vector* an, *bn, *cn;
		struct SolidVertex* o;
		Vector norm;

		a = VEC_DATA(&sm->vertices) + VEC_DATA(&sm->vertexIndices)[i];
		b = VEC_DATA(&sm->vertices) + VEC_DATA(&sm->vertexIndices)[i+1];
		c = VEC_DATA(&sm->vertices) + VEC_DATA(&sm->vertexIndices)[i+2];

		if(sm->hasNormals) {
			an = VEC_DATA(&sm->normals) + VEC_DATA(&sm->normalIndices)[i];
			bn = VEC_DATA(&sm->normals) + VEC_DATA(&sm->normalIndices)[i+1];
			cn = VEC_DATA(&sm->normals) + VEC_DATA(&sm->normalIndices)[i+2];
		}
		else { // use face normals for now
			vTriFaceNormal(a, b, c, &norm);
			an = bn = cn = &norm;
		}
		o = faces + i;

		// calculate the face normal
		//vTriFaceNormal(a, b, c, &norm);

		//printf("face %d: [%.2f,%.2f,%.2f] [%.2f,%.2f,%.2f] [%.2f,%.2f,%.2f] \n", i/3, a->x, a->y, a->z, b->x, b->y, b->z, c->x, c->y, c->z);
		// fill in the face info
		vCopy(a, &o->pos);
		vCopy(b, &o[1].pos);
		vCopy(c, &o[2].pos);

		vCopy(an, &o->face_normal);
		vCopy(bn, &(o+1)->face_normal);
		vCopy(cn, &(o+2)->face_normal);

		o->texcoords.u = VEC_DATA(&sm->texcoords)[VEC_DATA(&sm->textureIndices)[i]].x;
		o->texcoords.v = VEC_DATA(&sm->texcoords)[VEC_DATA(&sm->textureIndices)[i]].y;
		(o+1)->texcoords.u = VEC_DATA(&sm->texcoords)[VEC_DATA(&sm->textureIndices)[i+1]].x;
		(o+1)->texcoords.v = VEC_DATA(&sm->texcoords)[VEC_DATA(&sm->textureIndices)[i+1]].y;
		(o+2)->texcoords.u = VEC_DATA(&sm->texcoords)[VEC_DATA(&sm->textureIndices)[i+2]].x;
		(o+2)->texcoords.v = VEC_DATA(&sm->texcoords)[VEC_DATA(&sm->textureIndices)[i+2]].y;
	}

	VAOConfig opts[] = {
		// per vertex
		{3, GL_FLOAT}, // position
		{3, GL_FLOAT}, // face_normal
		{2, GL_FLOAT}, // tex
		{0, 0}
	};

	sm->solid = renderable_Create(GL_TRIANGLES, "staticMesh", opts, faces, face_cnt * 3);

	if(sm->texPath) {
		Renderable_applyTexturePath(sm->solid, sm->texPath);
	}

	// set status flag
}

static uint64_t edgeLookupHash(void* key) {
	return HT_HashFn(key, sizeof(uint64_t));
}

static int edgeLookupCmp(void* a, void* b) {
	return (uint64_t)a - (uint64_t)b;
}

static inline uint64_t makeEdgeKey(uint32_t a, uint32_t b) {
	return MIN(a, b) | MAX(a, b) << 32;
}

static void generateWireframe(StaticMesh* sm) {
	/*
	int i, edge_cnt, edge_alloc;

	Vector* edges;

	// allocate for the worst case scenario of a bunch of independent triangles
	edge_alloc = sm->faceCnt * 3 * 2;
	edge_cnt = 0;

	edges = malloc(edge_alloc * sizeof(*edges));
	CHECK_OOM(edges);

	// initialize edge lookup hash table
	sm->edgeLookup = HT_createCustom(0, edgeLookupHash, edgeLookupCmp);

	// edges are considered going from lowest to highest index
	for(i = 0; i < sm->indexCnt; i += 3) {
		uint32_t ai, bi, ci;
		uint64_t e_ab, e_bc, e_ca;
		Vector* a, *b, *c, *o;
		Vector norm;

		ai = sm->indices[i];
		bi = sm->indices[i+1];
		ci = sm->indices[i+2];

		a = VEC_DATA(&sm->vertices) + ai;
		b = VEC_DATA(&sm->vertices) + bi;
		c = VEC_DATA(&sm->vertices) + ci;

		e_ab = makeEdgeKey(ai, bi);
		e_bc = makeEdgeKey(ai, bi);
		e_ca = makeEdgeKey(ci, ai);

		// TODO: unique edge index table?

		if(HT_get(sm->edgeLookup, (void*)e_ab, NULL)) {
			// not found, add edge
			vCopy(a, edges + edge_cnt);
			vCopy(b, edges + edge_cnt + 1);

			HT_set(sm->edgeLookup, (void*)e_ab, 1);
			edge_cnt++;
		}

		if(HT_get(sm->edgeLookup, (void*)e_bc, NULL)) {
			// not found, add edge
			vCopy(b, edges + edge_cnt);
			vCopy(c, edges + edge_cnt + 1);

			HT_set(sm->edgeLookup, (void*)e_bc, 1);
			edge_cnt++;
		}

		if(HT_get(sm->edgeLookup, (void*)e_ab, NULL)) {
			// not found, add edge
			vCopy(c, edges + edge_cnt);
			vCopy(a, edges + edge_cnt + 1);

			HT_set(sm->edgeLookup, (void*)e_ca, 1);
			edge_cnt++;
		}

	}

	// TODO: realloc?

	VAOConfig opts[] = {
		// per vertex
		{3, GL_FLOAT}, // position
		{3, GL_FLOAT}, // face_normal
		{0, 0}
	};

	sm->wireframe = renderable_Create(GL_LINES, "staticMeshWireframe", opts, edges, edge_cnt);

	// TODO: set status flag
	*/
}

static void generatePoints(StaticMesh* sm) {

	VAOConfig opts[] = {
		// per vertex
		{3, GL_FLOAT}, // position
		{0, 0}
	};

	sm->points = renderable_Create(GL_POINTS, "staticMeshPoints", opts, VEC_DATA(&sm->vertices), VEC_LEN(&sm->vertices));

	// TODO: set status flag
}

void generateBBox(StaticMesh* sm) {

	BoundingBoxVisual_update(
		&sm->bbox,
		&sm->aabbMin,
		&sm->aabbMax
	);

	Vector v = {5,5,5};
	AxesVisual_init(&sm->av, &v);
}

void staticMesh_RegenMeta(StaticMesh* sm) {

	StaticMesh_CalcStats(sm);

	generateSolid(sm);
//	generateWireframe(sm);
	generatePoints(sm);

	generateBBox(sm);

}

void staticMesh_Init(StaticMesh* sm) {

	VEC_INIT(&sm->vertices);
	VEC_INIT(&sm->normals);
	VEC_INIT(&sm->texcoords);

	VEC_INIT(&sm->vertexIndices);
	VEC_INIT(&sm->normalIndices);
	VEC_INIT(&sm->textureIndices);

	VEC_INIT(&sm->edges);

}

StaticMesh* staticMesh_Create() {
	StaticMesh* sm;

	sm = calloc(1, sizeof(*sm));
	CHECK_OOM(sm);

	staticMesh_Init(sm);

	return sm;
}

// hacky for now
StaticMesh* staticMesh_FromPLY(PLYContents* pc) {
	int i;
	Vector null = {0,0,0};
	StaticMesh* sm;

	sm = staticMesh_Create();

	VEC_COPY(&sm->vertices, &pc->vertices);
	VEC_COPY(&sm->vertexIndices, &pc->faces);

	sm->hasTexCoords = 1;
	VEC_COPY(&sm->texcoords, &pc->texcoords);
	VEC_COPY(&sm->textureIndices, &pc->indicesTex);

	sm->hasNormals = 0;

	if(pc->texPath) sm->texPath = strdup(pc->texPath);

	return sm;
}

// hacky for now, sloppily recreates index data
StaticMesh* staticMesh_LoadOBJ(char* path) {
	int i;
	StaticMesh* sm;
	char* f, *raw;
	int chars_read;
	Vector tmp;

	int tc, vc, nc;
	tc = vc = nc = 0;

	raw = readFile(path, NULL);
	if(!raw) return;

	sm = staticMesh_Create();

	f = raw;
	while(*f) {
		char c;
		int fd[3][4];
		int n, ret;

		c = *f;
		f++;

		if(c == 'f') {

			// this loop decomposes polygons into fans of individual triangles
			ret = parseFaceVertex(&f, &fd[0]); // the center vertex
			ret = parseFaceVertex(&f, &fd[1]); // the next vertex
			do {
				ret = parseFaceVertex(&f, &fd[2]);
				if(ret) break;

				VEC_PUSH(&sm->vertexIndices,  fd[0][0]-1);
				VEC_PUSH(&sm->textureIndices, fd[0][1]-1);
				VEC_PUSH(&sm->normalIndices,  fd[0][2]-1);

				VEC_PUSH(&sm->vertexIndices,  fd[1][0]-1);
				VEC_PUSH(&sm->textureIndices, fd[1][1]-1);
				VEC_PUSH(&sm->normalIndices,  fd[1][2]-1);

				VEC_PUSH(&sm->vertexIndices,  fd[2][0]-1);
				VEC_PUSH(&sm->textureIndices, fd[2][1]-1);
				VEC_PUSH(&sm->normalIndices,  fd[2][2]-1);

				// shift the data down
				memcpy(&fd[1], &fd[2], sizeof(fd[2]));
			} while(1);
		}
		else if(c == 'v') {

			c = *f;
			f++;
			if(c == ' ') { // vertex

				chars_read = 0;
				n = sscanf(f, " %f %f %f%n", &tmp.x, &tmp.y, &tmp.z, &chars_read);
				VEC_PUSH(&sm->vertices, tmp);

				if(n < 3) fprintf(stderr, "error reading vertex\n");

				f += chars_read-1;
				vc++;
			}
			else if(c == 't') { // tex coord
				chars_read = 0;
				tmp.z = 0.0f;
				n = sscanf(f, " %f %f%n", &tmp.x, &tmp.y, &chars_read);
				VEC_PUSH(&sm->texcoords, tmp);

				if(n < 2) fprintf(stderr, "error reading tex coord\n");

				f += chars_read-1;
				tc++;
			}
			else if(c == 'n') { // normal
				chars_read = 0;
				n = sscanf(f, " %f %f %f%n", &tmp.x, &tmp.y, &tmp.z, &chars_read);
				VEC_PUSH(&sm->normals, tmp);

				if(n < 3) fprintf(stderr, "error reading normal\n");

				f += chars_read-1;
				nc++;
			}

		}
		else if(c == 'o') {

		}
		else if(c == 'm') {

		}

		// skip to the end of the line
		while(*f && *f != '\n') f++;
		f++;
	}

	printf("static mesh vertices: %d\n", VEC_LEN(&sm->vertices));
	printf("static mesh indices: %d\n", VEC_LEN(&sm->vertexIndices));

	return sm;
}

int static parseFaceVertex(char** s, int* info) {
	char* end;
	int val;
	int err, chars_read;

	while(**s && (**s == ' ' || **s == '\t' || **s == '\r')) (*s)++;
	if(**s == '\n') return 1;

	info[3] = 0;

	// first
	err = strtol_2(*s, &chars_read, &val);
	if(err) return 1;
	*s += chars_read ;
	info[0] = val;
	info[3]++;

	if(**s != '/') return 0;
	(*s)++;

	err = strtol_2(*s, &chars_read, &val);
	if(err) return 0;
	*s += chars_read ;
	info[1] = val;
	info[3]++;

	if(**s != '/') return 0;
	(*s)++;

	err = strtol_2(*s, &chars_read, &val);
	if(err) return 0;
	*s += chars_read ;
	info[2] = val;
	info[3]++;

	return 0;
}

void drawStaticMesh(StaticMesh* m, Matrix* view, Matrix* proj) {

	m->solid->scale = 1;
	renderable_Draw(m->solid, view, proj);
	renderable_Draw(m->bbox.rend, view, proj);
	renderable_Draw(m->bbox.rendAxes, view, proj);

	AxesVisual_render(&m->av, view, proj);

//	renderable_Draw(m->points, view, proj);
}

void StaticMesh_CalcStats(StaticMesh* sm) {
	int i;

	// for calculating the average center point
	struct { double x,y,z; } sums;

	// for calculating the most extreme points
	float px, py, pz, mx, my, mz;
	px = py = pz = -FLT_MAX;
	mx = my = mz = FLT_MAX;

	for(i = 0; i < VEC_LEN(&sm->vertices); i++) {
		Vector* v = &VEC_ITEM(&sm->vertices, i);

		if(v->x < mx) { mx = v->x; sm->extremes.m_x; }
		if(v->x > px) { px = v->x; sm->extremes.p_x; }
		if(v->y < my) { my = v->y; sm->extremes.m_y; }
		if(v->y > py) { py = v->y; sm->extremes.p_y; }
		if(v->z < mz) { mz = v->z; sm->extremes.m_z; }
		if(v->z > pz) { pz = v->z; sm->extremes.p_z; }

		sums.x += v->x;
		sums.y += v->y;
		sums.z += v->z;
	}

	sm->aabbMin.x = mx;
	sm->aabbMin.y = my;
	sm->aabbMin.z = mz;
	sm->aabbMax.x = px;
	sm->aabbMax.y = py;
	sm->aabbMax.z = pz;

	sm->aabbDimensions.x = (px - mx);
	sm->aabbDimensions.y = (py - my);
	sm->aabbDimensions.z = (pz - mz);
	sm->aabbCenter.x = (sm->aabbDimensions.x / 2) + mx;
	sm->aabbCenter.y = (sm->aabbDimensions.y / 2) + my;
	sm->aabbCenter.z = (sm->aabbDimensions.z / 2) + mz;

	sm->meanCenter.x = sums.x / VEC_LEN(&sm->vertices);
	sm->meanCenter.y = sums.y / VEC_LEN(&sm->vertices);
	sm->meanCenter.z = sums.z / VEC_LEN(&sm->vertices);

}


code:
#ifndef __EACSMB_STATICMESH_H__
#define __EACSMB_STATICMESH_H__

#include "hash.h"
#include "renderable.h"
#include "bbox.h"

typedef struct StaticMeshVertex {
	Vector v, n;
	struct {
		float u, v;
	} t;
} StaticMeshVertex;

typedef struct StaticMesh {

	char ready;

	// always GL_TRIANGLES
	VEC(Vector) vertices;
	VEC(Vector) normals;
	VEC(Vector) texcoords;
	VEC(uint32_t) vertexIndices;
	VEC(uint32_t) normalIndices;
	VEC(uint32_t) textureIndices;

	char hasNormals;
	char hasTexCoords;

	char* texPath;
	Texture* texture;

	//int vertexCnt;
	int faceCnt;
	//int indexCnt; // = faceCnt * 3

	VEC(int*) edges; // indices into vertices

	HashTable* edgeLookup;

	// adjacency info
	int* triAdjacency; // uses faceCnt;

	// vertex-vertex adj
	// vertex-tri adj
	// edge-edge adj

	// stats
	Vector aabbMin, aabbMax;

	// vertex indices
	struct {
		uint32_t p_x, m_x;
		uint32_t p_y, m_y;
		uint32_t p_z, m_z;
	} extremes;

	Vector aabbDimensions;
	Vector aabbCenter;
	Vector meanCenter;

	// rendering stuff
	BoundingBoxVisual bbox;

	AxesVisual av;

	Renderable* solid;
	Renderable* wireframe;
	Renderable* points;

	char showSolid;
	char showWireframe;
	char showPoints;

	Vector pos;
	Vector raxis;
	float rtheta;
	float scale;

	Matrix composed;

} StaticMesh;

StaticMesh* staticMesh_Create();
void staticMesh_Destroy(StaticMesh** sm);

void staticMesh_RegenMeta(StaticMesh* sm);
float StaticMesh_setScale(StaticMesh* sm, float newscale);

void initStaticMeshes();
StaticMesh* StaticMeshFromOBJ(OBJContents* obj);
StaticMesh* StaticMesh_LoadOBJ(char* path);
StaticMesh* staticMesh_FromPLY(PLYContents* pc);

void StaticMesh_CalcStats(StaticMesh* sm);

void drawStaticMesh(StaticMesh* m, Matrix* view, Matrix* proj);

#endif // __EACSMB_STATICMESH_H__


code:
#include <stdio.h>
#include <stdlib.h>
#include <fontconfig/fontconfig.h>

#include "fcfg.h"

static FcConfig* config = NULL;

void initFontConfig() {
	config = FcInitLoadConfigAndFonts();
}

char* getFontFile(char* fontName) {
	FcPattern* pattern, *font;
	FcResult result;
	char* fileName = NULL;

	if(!config) initFontConfig();

	pattern = FcNameParse((const FcChar8*)fontName);
	FcConfigSubstitute(config, pattern, FcMatchPattern);
	FcDefaultSubstitute(pattern);

	font = FcFontMatch(config, pattern, &result);

	if(!(font && FcPatternGetString(font, FC_FILE, 0, (FcChar8**)&fileName) == FcResultMatch)) {
		fprintf(stderr, "Could not find a font file for '%s'\n", fontName);
	}

	FcPatternDestroy(pattern);

	return fileName;
}