Index: trunk/nv/formats/md3_loader.hh
===================================================================
--- trunk/nv/formats/md3_loader.hh	(revision 148)
+++ trunk/nv/formats/md3_loader.hh	(revision 148)
@@ -0,0 +1,52 @@
+// Copyright (C) 2012-2013 ChaosForge / Kornel Kisielewicz
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+
+/**
+ * @file md3_loader.hh
+ * @author Kornel Kisielewicz
+ * @brief md3 loader
+ */
+
+#ifndef NV_MD3_LOADER_HH
+#define NV_MD3_LOADER_HH
+
+#include <unordered_map>
+#include <vector>
+#include <nv/common.hh>
+#include <nv/interface/mesh_loader.hh>
+
+namespace nv 
+{
+	struct md3_tag
+	{
+		std::string name;
+		mat4 transform;
+	};
+
+	class md3_loader : public mesh_loader
+	{
+	public:
+		md3_loader();
+		virtual ~md3_loader();
+		virtual bool load( stream& source );
+		virtual size_t get_size() { return m_size; }
+		virtual mesh* release_mesh();
+		virtual mesh* get_frame( sint32 frame );
+		virtual const md3_tag* get_tag( const std::string& name ) const;
+		virtual mat4 get_tag( sint32 frame, const std::string& name ) const;
+		void load_positions( std::vector<vec3>& p, sint32 frame =-1 );
+		void load_normals( std::vector<vec3>& n, sint32 frame =-1 );
+		void load_texcoords( std::vector<vec2>& t );
+		void load_indicies( std::vector<uint16>& idx );
+	private:
+		void* m_md3;
+		std::unordered_map< std::string, md3_tag > m_tags;
+		std::size_t m_size;
+	};
+
+}
+
+#endif // NV_OBJ_LOADER_HH
Index: trunk/nv/interface/mesh.hh
===================================================================
--- trunk/nv/interface/mesh.hh	(revision 147)
+++ trunk/nv/interface/mesh.hh	(revision 148)
@@ -80,6 +80,7 @@
 		{ 
 			if ( m_indices ) delete m_indices; // error?
-			m_indices = new vertex_attribute<T>(""); 
-			return m_indices;
+			vertex_attribute<T>* result = new vertex_attribute<T>(""); 
+			m_indices = result; 
+			return result;
 		}
 
Index: trunk/nv/interface/stream.hh
===================================================================
--- trunk/nv/interface/stream.hh	(revision 147)
+++ trunk/nv/interface/stream.hh	(revision 148)
@@ -17,9 +17,10 @@
 namespace nv
 {
+
 	enum class origin : uint32 
 	{
-		SET = 0x00,
-		CUR = 0x01,
-		END = 0x02,
+		SET = 0,
+		CUR = 1,
+		END = 2,
 	};
 
Index: trunk/src/formats/md3_loader.cc
===================================================================
--- trunk/src/formats/md3_loader.cc	(revision 148)
+++ trunk/src/formats/md3_loader.cc	(revision 148)
@@ -0,0 +1,438 @@
+// Copyright (C) 2012-2013 ChaosForge / Kornel Kisielewicz
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+
+#include "nv/formats/md3_loader.hh"
+
+#include <glm/gtc/constants.hpp>
+#include <glm/gtx/string_cast.hpp>
+#include "nv/logging.hh"
+#include <cstring>
+
+using namespace nv;
+
+// based on http://www.icculus.org/~phaethon/q3/formats/md3format.html#Surface
+
+// assuming low-endian
+#define MD3_MAGIC         "IDP3"
+#define MD3_MAX_FRAMES    1024
+#define MD3_MAX_TAGS      16
+#define MD3_MAX_SURFACES  32
+#define MD3_MAX_SHADERS   256
+#define MD3_MAX_VERTS     4096
+#define MD3_MAX_TRIANGLES 8192
+#define MD3_XYZ_SCALE     (1.0f/64.0f)
+
+struct md3_vec3_t
+{
+	float xyz[3];
+};
+
+struct md3_header_t
+{
+	char   ident[4]; // IDP3
+	sint32 version;  // 15
+	uint8  name[64]; // path name
+	sint32 flags;
+	sint32 num_frames;     // Number of Frame objects, with a maximum of MD3_MAX_FRAMES. Current value of MD3_MAX_FRAMES is 1024.
+	sint32 num_tags;       // Number of Tag objects, with a maximum of MD3_MAX_TAGS. Current value of MD3_MAX_TAGS is 16.
+	sint32 num_surfaces;   // Number of Surface objects, with a maximum of MD3_MAX_SURFACES. Current value of MD3_MAX_SURFACES is 32.
+	sint32 num_skins;      // Number of Skin objects. I should note that I have not seen an MD3 using this particular field for anything; this appears to be an artifact from the Quake 2 MD2 format. Surface objects have their own Shader field.
+	sint32 ofs_frames;     // Relative offset from start of MD3 object where Frame objects start. The Frame objects are written sequentially, that is, when you read one Frame object, you do not need to seek() for the next object.
+	sint32 ofs_tags;       // Relative offset from start of MD3 where Tag objects start. Similarly written sequentially.
+	sint32 ofs_surfaces;   // Relative offset from start of MD3 where Surface objects start. Again, written sequentially.
+	sint32 ofs_eof;        // Relative offset from start of MD3 to the end of the MD3 object. Note there is no offset for Skin objects.
+};
+
+struct md3_frame_t
+{
+	md3_vec3_t min_bounds;
+	md3_vec3_t max_bounds;
+	md3_vec3_t local_origin;
+	float      radius;
+	uint8      name[16];
+};
+
+struct md3_tag_t
+{
+	uint8      name[64];
+	md3_vec3_t origin;
+	md3_vec3_t axis[3];
+};
+
+struct md3_surface_header_t
+{
+	char   ident[4]; // IDP3
+	uint8  name[64]; // path name
+	sint32 flags;
+	sint32 num_frames;
+	sint32 num_shaders;
+	sint32 num_verts;
+	sint32 num_triangles;
+	sint32 ofs_triangles;
+	sint32 ofs_shaders;
+	sint32 ofs_st;
+	sint32 ofs_xyznormal;
+	sint32 ofs_end;
+};
+
+struct md3_shader_t
+{
+	uint8  name[64]; 
+	sint32 shader_index;
+};
+
+struct md3_triangle_t
+{
+	sint32 indexes[3];
+};
+
+struct md3_texcoord_t
+{
+	float  st[2];
+};
+
+struct md3_vertex_t
+{
+	sint16 x;
+	sint16 y;
+	sint16 z;
+	sint16 normal;
+};
+
+struct md3_surface_t
+{
+	md3_surface_header_t header;
+	md3_shader_t*        shaders;
+	md3_triangle_t*      triangles;
+	md3_texcoord_t*      st;
+	md3_vertex_t*        vertices;
+};
+
+struct md3_t
+{
+	md3_header_t   header;
+	md3_frame_t*   frames;
+	md3_tag_t*     tags;
+	md3_surface_t* surfaces;
+};
+
+static bool check_md3_magic( char* magic )
+{
+	return magic[0] == 'I' && magic[1] == 'D' && magic[2] == 'P' && magic[3] == '3';
+}
+
+static void free_md3_surface( md3_surface_t * surface )
+{
+	delete[] surface->shaders;
+	delete[] surface->triangles;
+	delete[] surface->st;
+	delete[] surface->vertices;
+}
+
+static void free_md3( md3_t * md3 )
+{
+	sint32 count = md3->header.num_surfaces;
+	for ( sint32 i = 0; i < count; ++i )
+	{
+		free_md3_surface( &md3->surfaces[i] );
+	}
+	delete[] md3->frames;
+	delete[] md3->tags;
+	delete[] md3->surfaces;
+}
+
+static bool read_surface( md3_surface_t * surface, nv::stream& source )
+{
+	sint32 pos = source.tell();
+	source.read( &surface->header, sizeof(md3_surface_header_t), 1 );
+
+	if ( !check_md3_magic( surface->header.ident ) )          return false;
+	if ( surface->header.num_frames    >  MD3_MAX_FRAMES )    return false;
+	if ( surface->header.num_shaders   >  MD3_MAX_SHADERS )   return false;
+	if ( surface->header.num_verts     >  MD3_MAX_VERTS )     return false;
+	if ( surface->header.num_triangles >  MD3_MAX_TRIANGLES ) return false;
+
+	surface->shaders   = new md3_shader_t  [ surface->header.num_shaders ];
+	surface->vertices  = new md3_vertex_t  [ surface->header.num_verts * surface->header.num_frames ];
+	surface->st        = new md3_texcoord_t[ surface->header.num_verts ];
+	surface->triangles = new md3_triangle_t[ surface->header.num_triangles ];
+
+	source.seek( pos + surface->header.ofs_shaders, origin::SET );
+	source.read( surface->shaders, sizeof( md3_shader_t ), surface->header.num_shaders );
+
+	source.seek( pos + surface->header.ofs_triangles, origin::SET );
+	source.read( surface->triangles, sizeof( md3_triangle_t ), surface->header.num_triangles );
+
+	source.seek( pos + surface->header.ofs_st, origin::SET );
+	source.read( surface->st, sizeof( md3_texcoord_t ), surface->header.num_verts );
+
+	source.seek( pos + surface->header.ofs_xyznormal, origin::SET );
+	source.read( surface->vertices, sizeof( md3_vertex_t ), surface->header.num_verts * surface->header.num_frames );
+
+	if ( source.tell() != static_cast<std::size_t>( pos + surface->header.ofs_end ) ) return false;
+
+	return true;
+}
+
+static bool read_md3( md3_t * md3, nv::stream& source )
+{
+	md3->frames   = nullptr;
+	md3->tags     = nullptr;
+	md3->surfaces = nullptr;
+
+	source.read( &md3->header, sizeof(md3_header_t), 1 );
+
+	if ( !check_md3_magic( md3->header.ident ) )        return false;
+	if ( md3->header.num_frames   >  MD3_MAX_FRAMES )   return false;
+	if ( md3->header.num_tags     >  MD3_MAX_TAGS )     return false;
+	if ( md3->header.num_surfaces >  MD3_MAX_SURFACES ) 
+	{
+		// to always have a safe free
+		md3->header.num_surfaces = 0;
+		return false;
+	}
+
+	md3->frames   = new md3_frame_t  [ md3->header.num_frames ];
+	md3->tags     = new md3_tag_t    [ md3->header.num_tags * md3->header.num_frames ];
+	md3->surfaces = new md3_surface_t[ md3->header.num_surfaces ];
+	std::memset( md3->surfaces, 0, md3->header.num_surfaces * sizeof(md3_surface_t) );
+
+	source.seek( md3->header.ofs_frames, origin::SET );
+	source.read( md3->frames, sizeof( md3_frame_t ), md3->header.num_frames );
+
+	source.seek( md3->header.ofs_tags, origin::SET );
+	source.read( md3->tags, sizeof( md3_tag_t ), md3->header.num_tags * md3->header.num_frames );
+
+	source.seek( md3->header.ofs_surfaces, origin::SET );
+
+	for ( sint32 i = 0; i < md3->header.num_surfaces; ++i )
+	{
+		if ( !read_surface( md3->surfaces + i, source ) ) return false;
+		if ( md3->header.num_frames != md3->surfaces[i].header.num_frames ) return false;
+	}
+	return true;
+}
+
+static inline vec3 md3_vec3( const md3_vec3_t& v )
+{
+//	return vec3( v.xyz[0], v.xyz[1], v.xyz[2] );
+	return vec3( v.xyz[0], v.xyz[2], v.xyz[1] );
+}
+
+static inline vec2 md3_texcoord( const md3_texcoord_t& v )
+{
+	return vec2( v.st[0], v.st[1] );
+}
+
+static inline vec3 md3_vec3( const md3_vertex_t& v )
+{
+//	return vec3( v.x * MD3_XYZ_SCALE, v.y * MD3_XYZ_SCALE, v.z * MD3_XYZ_SCALE );
+	return vec3( v.x * MD3_XYZ_SCALE, v.z * MD3_XYZ_SCALE, v.y * MD3_XYZ_SCALE );
+}
+
+static inline vec3 md3_normal( const md3_vertex_t& v )
+{
+	float pi  = glm::pi<float>();
+	float lat = (((v.normal >> 8) & 255) * (2 * pi)) / 255.0f;
+	float lng = ((v.normal & 255) * (2 * pi)) / 255.0f;
+	return vec3( 
+		glm::cos( lat ) * glm::sin( lng ), 
+//		glm::sin( lat ) * glm::sin( lng ), 
+//		glm::cos( lng )
+		glm::cos( lng ),
+		glm::sin( lat ) * glm::sin( lng )
+	);
+}
+
+
+md3_loader::md3_loader()
+	: m_md3( nullptr ), m_size( 0 )
+{
+	
+}
+
+
+nv::md3_loader::~md3_loader()
+{
+	if (m_md3 != nullptr)
+	{
+		free_md3( (md3_t*)(m_md3) );
+		delete (md3_t*)m_md3;
+	}
+}
+
+bool nv::md3_loader::load( stream& source )
+{
+	m_tags.clear();
+	m_size = 0;
+
+	m_md3 = (void*)(new md3_t);
+	if ( !read_md3( (md3_t*)m_md3, source ) )
+	{
+		return false;
+	}
+	return true;
+}
+
+mesh* nv::md3_loader::release_mesh()
+{
+	return get_frame( 0 );
+}
+
+mesh* nv::md3_loader::get_frame( sint32 frame )
+{
+	mesh* m = new mesh();
+	md3_t* md3 = (md3_t*)m_md3;
+
+	NV_LOG( LOG_INFO, "Tags:" );
+	for ( sint32 i = 0; i < md3->header.num_tags; ++i )
+	{
+		const md3_tag_t& rtag = md3->tags[i + md3->header.num_tags * frame];
+
+		md3_tag& tag   = m_tags[ (char*)(rtag.name) ];
+		tag.name       = (char*)(rtag.name);
+		vec4 axisx     = vec4( md3_vec3( rtag.axis[0] ), 0.0 );
+		vec4 axisz     = vec4( md3_vec3( rtag.axis[1] ), 0.0 );
+		vec4 axisy     = vec4( md3_vec3( rtag.axis[2] ), 0.0 );
+		vec4 origin    = vec4( md3_vec3( rtag.origin ),  1.0 );
+		tag.transform  = glm::mat4( axisx, axisy, axisz, origin );
+		NV_LOG( LOG_INFO, "Tag " << tag.name << " found" );
+	}
+
+	vertex_attribute< vec3 >* position = m->add_attribute<vec3>("position");
+	vertex_attribute< vec3 >* normal   = m->add_attribute<vec3>("normal");
+	vertex_attribute< vec2 >* texcoord = m->add_attribute<vec2>("texcoord");
+	vertex_attribute< uint16 >* indices  = m->add_indices<uint16>();
+
+	load_positions( position->get(), frame );
+	load_normals( normal->get(), frame );
+
+	load_texcoords( texcoord->get() );
+	load_indicies( indices->get() );
+
+	m_size = indices->get().size();
+	return m;
+}
+
+mat4 md3_loader::get_tag( sint32 frame, const std::string& name ) const
+{
+	md3_t* md3 = (md3_t*)m_md3;
+	for ( sint32 i = 0; i < md3->header.num_tags; ++i )
+	{
+		const md3_tag_t& rtag = md3->tags[i + md3->header.num_tags * frame];
+		std::string rname((char*)(rtag.name));
+		if (rname == name)
+		{
+			vec4 axisx     = vec4( md3_vec3( rtag.axis[0] ), 0.0 );
+			vec4 axisz     = vec4( md3_vec3( rtag.axis[1] ), 0.0 );
+			vec4 axisy     = vec4( md3_vec3( rtag.axis[2] ), 0.0 );
+			vec4 origin    = vec4( md3_vec3( rtag.origin ),  1.0 );
+			return glm::mat4( axisx, axisy, axisz, origin );
+		}
+	}
+	return glm::mat4();
+}
+
+const md3_tag* md3_loader::get_tag( const std::string& name ) const
+{
+	auto it = m_tags.find( name );
+	if ( it == m_tags.end() ) return nullptr;
+	return &(it->second);
+}
+
+void md3_loader::load_positions( std::vector<vec3>& p, sint32 frame /*=-1*/ )
+{
+	md3_t* md3 = (md3_t*)m_md3;
+	sint32 num_surfaces = md3->header.num_surfaces;
+	p.clear();
+	sint32 current_frame = ( frame == -1 ? 0 : frame );
+	sint32 frame_count   = ( frame == -1 ? md3->header.num_frames : 1 );
+
+	while ( frame_count > 0 )
+	{
+		for ( sint32 i = 0; i < num_surfaces; ++i )
+		{
+			md3_surface_t& surface = md3->surfaces[i];
+			sint32         vcount  = surface.header.num_verts;
+			sint32         offset  = vcount * current_frame;
+			p.reserve( p.size() + vcount );
+			for (sint32 j = 0; j < vcount; ++j )
+			{
+				p.push_back( md3_vec3( surface.vertices[j + offset] ) );
+			}
+		}
+		++current_frame;
+		--frame_count;
+	}
+}
+
+void md3_loader::load_normals( std::vector<vec3>& n, sint32 frame /*=-1*/ )
+{
+	md3_t* md3 = (md3_t*)m_md3;
+	sint32 num_surfaces = md3->header.num_surfaces;
+	n.clear();
+	sint32 current_frame = ( frame == -1 ? 0 : frame );
+	sint32 frame_count   = ( frame == -1 ? md3->header.num_frames : 1 );
+
+	while ( frame_count > 0 )
+	{
+		for ( sint32 i = 0; i < num_surfaces; ++i )
+		{
+			md3_surface_t& surface = md3->surfaces[i];
+			sint32         vcount  = surface.header.num_verts;
+			sint32         offset  = vcount * current_frame;
+			n.reserve( n.size() + vcount );
+			for (sint32 j = 0; j < vcount; ++j )
+			{
+				n.push_back( md3_normal( surface.vertices[j + offset] ) );
+			}
+		}
+		++current_frame;
+		--frame_count;
+	}
+}
+
+void md3_loader::load_texcoords( std::vector<vec2>& t )
+{
+	md3_t* md3 = (md3_t*)m_md3;
+	sint32 num_surfaces = md3->header.num_surfaces;
+	t.clear();
+	for ( sint32 i = 0; i < num_surfaces; ++i )
+	{
+		const md3_surface_t& surface = md3->surfaces[i];
+		const sint32         vcount  = surface.header.num_verts;
+		t.reserve( t.size() + vcount );
+		for (sint32 j = 0; j < vcount; ++j )
+		{
+			t.push_back( md3_texcoord( surface.st[j] ) );
+		}
+	}
+}
+
+void md3_loader::load_indicies( std::vector<uint16>& idx )
+{
+	md3_t* md3 = (md3_t*)m_md3;
+	sint32 num_surfaces = md3->header.num_surfaces;
+	idx.clear();
+	sint32 index_base = 0;
+	for ( sint32 i = 0; i < num_surfaces; ++i )
+	{
+		const md3_surface_t& surface = md3->surfaces[i];
+		const sint32         tcount  = surface.header.num_triangles;
+
+		idx.reserve( idx.size() + tcount * 3 );
+		for (sint32 j = 0; j < tcount; ++j )
+		{
+			const md3_triangle_t& t = surface.triangles[j];
+			idx.push_back( static_cast< uint16 >( index_base + t.indexes[0] ) );
+			idx.push_back( static_cast< uint16 >( index_base + t.indexes[1] ) );
+			idx.push_back( static_cast< uint16 >( index_base + t.indexes[2] ) );
+		}
+
+		index_base += surface.header.num_verts;
+	}
+
+}
Index: trunk/src/formats/obj_loader.cc
===================================================================
--- trunk/src/formats/obj_loader.cc	(revision 147)
+++ trunk/src/formats/obj_loader.cc	(revision 148)
@@ -194,4 +194,6 @@
 		uint32 i3 = a * 3 + 2;
 
+		// TODO: simplify
+
 		const vec3& v1 = vp[i1];
 		const vec3& v2 = vp[i2];
@@ -206,12 +208,4 @@
 		vec2 st1  = w2 - w1;
 		vec2 st2  = w3 - w1;
-
-		float x1 = v2.x - v1.x;
-		float y1 = v2.y - v1.y;
-		float z1 = v2.z - v1.z;
-
-		float x2 = v3.x - v1.x;
-		float y2 = v3.y - v1.y;
-		float z2 = v3.z - v1.z;
 
 		float s1 = w2.x - w1.x;
