// 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/obj_loader.hh" #include "nv/io/std_stream.hh" #include using namespace nv; struct obj_vertex_vt { vec3 position; vec2 texcoord; obj_vertex_vt( vec3 a_position, vec2 a_texcoord ) : position( a_position ), texcoord( a_texcoord ) {} }; struct obj_vertex_vtn { vec3 position; vec2 texcoord; vec3 normal; obj_vertex_vtn( vec3 a_position, vec2 a_texcoord, vec3 a_normal ) : position( a_position ), texcoord( a_texcoord ), normal( a_normal ) {} }; struct obj_vertex_vtnt { vec3 position; vec2 texcoord; vec3 normal; vec4 tangent; obj_vertex_vtnt( vec3 a_position, vec2 a_texcoord, vec3 a_normal ) : position( a_position ), texcoord( a_texcoord ), normal( a_normal ) {} }; struct obj_reader { std::vector< vec3 > v; std::vector< vec3 > n; std::vector< vec2 > t; std::string line; std::string cmd; std::size_t size; obj_reader(); bool read_stream( std::istream& stream ); virtual size_t add_face( uint32* vi, uint32* ti, uint32* ni, size_t count ) = 0; virtual size_t raw_size() { return 0; } virtual const uint8* raw_pointer() { return nullptr; } virtual void calculate_tangents() {} virtual ~obj_reader(){} }; obj_reader::obj_reader() { // push in dummy 0-index objects for faster indexing v.push_back( vec3() ); n.push_back( vec3() ); t.push_back( vec2() ); size = 0; } bool obj_reader::read_stream( std::istream& stream ) { f32 x, y, z; while ( std::getline( stream, line ) ) { if ( line.length() < 3 || line[0] == '#' ) { continue; } std::istringstream ss(line); ss >> cmd; if ( cmd == "v" ) { ss >> x >> y >> z; v.push_back( vec3( x, y, z ) ); continue; } if ( cmd == "vn" ) { ss >> x >> y >> z; n.push_back( vec3( x, y, z ) ); continue; } if ( cmd == "vt" ) { ss >> x >> y; t.push_back( vec2( x, 1.0f - y ) ); continue; } if ( cmd == "f" ) { ss >> cmd; uint32 vis[8]; uint32 tis[8]; uint32 nis[8]; bool normals = false; uint32 count = 0; while ( !ss.fail() ) { char ch; std::istringstream ss2( cmd ); ss2 >> vis[count] >> ch; ss2 >> tis[count] >> ch; if ( ch == '/') { normals = true; ss2 >> nis[count]; } ss >> cmd; count++; } size += add_face( vis, tis, normals ? nis : nullptr, count ); continue; } if ( cmd == "g" || cmd == "s" ) { // ignored continue; } // unknown command } return true; } struct mesh_obj_reader : public obj_reader { mesh_obj_reader( mesh_data_creator* m ) : m_mesh( m ) {} virtual std::size_t add_face( uint32* vi, uint32* ti, uint32* ni, size_t count ); virtual void calculate_tangents(); mesh_data_creator* m_mesh; }; size_t mesh_obj_reader::add_face( uint32* vi, uint32* ti, uint32* ni, size_t count ) { if ( count < 3 ) { // TODO : report error? return 0; } // TODO : support if normals not present; std::vector< vec3 >& vp = m_mesh->get_positions(); std::vector< vec3 >& vn = m_mesh->get_normals(); std::vector< vec2 >& vt = m_mesh->get_texcoords(); std::size_t result = 0; // Simple triangulation - obj's shouldn't have more than quads anyway for ( size_t i = 2; i < count; ++i ) { result++; vp.push_back( v[ vi[ 0 ] ] ); vt.push_back( t[ ti[ 0 ] ] ); vn.push_back( n[ ni[ 0 ] ] ); vp.push_back( v[ vi[ i-1 ] ] ); vt.push_back( t[ ti[ i-1 ] ] ); vn.push_back( n[ ni[ i-1 ] ] ); vp.push_back( v[ vi[ i ] ] ); vt.push_back( t[ ti[ i ] ] ); vn.push_back( n[ ni[ i ] ] ); } return result; } // based on http://www.terathon.com/code/tangent.html void mesh_obj_reader::calculate_tangents() { const std::vector< vec3 >& vp = m_mesh->get_positions(); const std::vector< vec2 >& vt = m_mesh->get_texcoords(); const std::vector< vec3 >& vn = m_mesh->get_normals(); std::vector< vec3 >& tg = m_mesh->get_tangents(); size_t count = vp.size(); size_t tcount = count / 3; std::vector< vec3 > tan1( count ); std::vector< vec3 > tan2( count ); tg.resize( count ); for (size_t a = 0; a < tcount; ++a ) { size_t i1 = a * 3; size_t i2 = a * 3 + 1; size_t i3 = a * 3 + 2; // TODO: simplify const vec3& v1 = vp[i1]; const vec3& v2 = vp[i2]; const vec3& v3 = vp[i3]; const vec2& w1 = vt[i1]; const vec2& w2 = vt[i2]; const vec2& w3 = vt[i3]; vec3 xyz1 = v2 - v1; vec3 xyz2 = v3 - v1; //vec2 st1 = w2 - w1; //vec2 st2 = w3 - w1; float s1 = w2.x - w1.x; float t1 = w2.y - w1.y; float s2 = w3.x - w1.x; float t2 = w3.y - w1.y; float stst = s1 * t2 - s2 * t1; float r = 0.0f; if (stst > 0.0f || stst < 0.0f) r = 1.0f / stst; vec3 sdir = ( t2 * xyz1 - t1 * xyz2 ) * r; vec3 tdir = ( s1 * xyz2 - s2 * xyz1 ) * r; // the += below obviously doesn't make sense in this case, but I'll // leave it here for when I move to indices tan1[i1] += sdir; tan1[i2] += sdir; tan1[i3] += sdir; // tan2 not needed anymore?? tan2[i1] += tdir; tan2[i2] += tdir; tan2[i3] += tdir; } for (std::size_t a = 0; a < count; ++a ) { const vec3& n = vn[a]; const vec3& t = tan1[a]; if ( ! (t.x == 0.0f && t.y == 0.0f && t.z == 0.0f) ) tg[a] = vec3( glm::normalize(t - n * glm::dot( n, t )) ); //tg[a][3] = (glm::dot(glm::cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f; } } nv::obj_loader::obj_loader( bool tangents ) : m_mesh( nullptr ), m_tangents( tangents ) { } nv::obj_loader::~obj_loader() { delete m_mesh; } bool nv::obj_loader::load( stream& source ) { if ( m_mesh != nullptr ) delete m_mesh; mesh_data_creator creator; mesh_obj_reader reader( &creator ); std_stream sstream( &source ); reader.read_stream( sstream ); m_size = reader.size; if ( m_tangents ) { reader.calculate_tangents(); } m_mesh = creator.release(); return true; } struct mesh_data_reader_vt : public obj_reader { mesh_data_reader_vt() {} virtual std::size_t add_face( uint32* vi, uint32* ti, uint32*, size_t count ) { if ( count < 3 ) return 0; // TODO : report error? // TODO : support if normals not present; std::size_t result = 0; // Simple triangulation - obj's shouldn't have more than quads anyway for ( size_t i = 2; i < count; ++i ) { result++; m_data.emplace_back( v[ vi[ 0 ] ], t[ ti[ 0 ] ] ); m_data.emplace_back( v[ vi[ i-1 ] ], t[ ti[ i-1 ] ] ); m_data.emplace_back( v[ vi[ i ] ], t[ ti[ i ] ] ); } return result; } std::vector< obj_vertex_vt > m_data; virtual size_t raw_size() { return m_data.size() * sizeof( obj_vertex_vt ); } virtual const uint8* raw_pointer() { return (const uint8*)m_data.data(); } }; struct mesh_data_reader_vtn : public obj_reader { mesh_data_reader_vtn() {} virtual std::size_t add_face( uint32* vi, uint32* ti, uint32* ni, size_t count ) { if ( count < 3 ) return 0; // TODO : report error? // TODO : support if normals not present; std::size_t result = 0; // Simple triangulation - obj's shouldn't have more than quads anyway for ( size_t i = 2; i < count; ++i ) { result++; m_data.emplace_back( v[ vi[ 0 ] ], t[ ti[ 0 ] ], n[ ni[ 0 ] ] ); m_data.emplace_back( v[ vi[ i-1 ] ], t[ ti[ i-1 ] ], n[ ni[ i-1 ] ] ); m_data.emplace_back( v[ vi[ i ] ], t[ ti[ i ] ], n[ ni[ i ] ] ); } return result; } std::vector< obj_vertex_vtn > m_data; virtual size_t raw_size() { return m_data.size() * sizeof( obj_vertex_vtn ); } virtual const uint8* raw_pointer() { return (const uint8*)m_data.data(); } }; struct mesh_data_reader_vtnt : public obj_reader { mesh_data_reader_vtnt() {} virtual std::size_t add_face( uint32* vi, uint32* ti, uint32* ni, size_t count ) { if ( count < 3 ) return 0; // TODO : report error? // TODO : support if normals not present; std::size_t result = 0; // Simple triangulation - obj's shouldn't have more than quads anyway for ( size_t i = 2; i < count; ++i ) { result++; m_data.emplace_back( v[ vi[ 0 ] ], t[ ti[ 0 ] ], n[ ni[ 0 ] ] ); m_data.emplace_back( v[ vi[ i-1 ] ], t[ ti[ i-1 ] ], n[ ni[ i-1 ] ] ); m_data.emplace_back( v[ vi[ i ] ], t[ ti[ i ] ], n[ ni[ i ] ] ); } return result; } std::vector< obj_vertex_vtnt > m_data; virtual size_t raw_size() { return m_data.size() * sizeof( obj_vertex_vtnt ); } virtual const uint8* raw_pointer() { return (const uint8*)m_data.data(); } // based on http://www.terathon.com/code/tangent.html void calculate_tangents() { // const std::vector< vec3 >& vp = m_mesh->get_positions(); // const std::vector< vec2 >& vt = m_mesh->get_texcoords(); // const std::vector< vec3 >& vn = m_mesh->get_normals(); // std::vector< vec3 >& tg = m_mesh->get_tangents(); size_t count = m_data.size(); size_t tcount = count / 3; std::vector< vec3 > tan1( count ); std::vector< vec3 > tan2( count ); for (size_t a = 0; a < tcount; ++a ) { size_t i1 = a * 3; size_t i2 = a * 3 + 1; size_t i3 = a * 3 + 2; obj_vertex_vtnt& vtx1 = m_data[ i1 ]; obj_vertex_vtnt& vtx2 = m_data[ i2 ]; obj_vertex_vtnt& vtx3 = m_data[ i3 ]; // TODO: simplify vec3 xyz1 = vtx2.position - vtx1.position; vec3 xyz2 = vtx3.position - vtx1.position; //vec2 st1 = w2 - w1; //vec2 st2 = w3 - w1; float s1 = vtx2.texcoord.x - vtx1.texcoord.x; float t1 = vtx2.texcoord.y - vtx1.texcoord.y; float s2 = vtx3.texcoord.x - vtx1.texcoord.x; float t2 = vtx3.texcoord.y - vtx1.texcoord.y; float stst = s1 * t2 - s2 * t1; float r = 0.0f; if (stst > 0.0f || stst < 0.0f) r = 1.0f / stst; vec3 sdir = ( t2 * xyz1 - t1 * xyz2 ) * r; vec3 tdir = ( s1 * xyz2 - s2 * xyz1 ) * r; // the += below obviously doesn't make sense in this case, but I'll // leave it here for when I move to indices tan1[i1] += sdir; tan1[i2] += sdir; tan1[i3] += sdir; // tan2 not needed anymore?? tan2[i1] += tdir; tan2[i2] += tdir; tan2[i3] += tdir; } for (std::size_t a = 0; a < count; ++a ) { const vec3& n = m_data[a].normal; const vec3& t = tan1[a]; if ( ! (t.x == 0.0f && t.y == 0.0f && t.z == 0.0f) ) { m_data[a].tangent = vec4( glm::normalize(t - n * glm::dot( n, t )), 0.0f ); m_data[a].tangent[3] = (glm::dot(glm::cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f; } } } }; nv::wavefront_loader::wavefront_loader( bool normals /*= true*/, bool tangents /*= false */ ) : m_normals( normals ), m_tangents( tangents ), m_mesh( nullptr ) { if ( normals ) { if ( tangents ) m_descriptor.initialize(); else m_descriptor.initialize(); } else m_descriptor.initialize(); } bool nv::wavefront_loader::load( stream& source ) { if ( m_mesh ) delete m_mesh; obj_reader* reader = nullptr; if ( m_normals ) { if ( m_tangents ) reader = new mesh_data_reader_vtnt(); else reader = new mesh_data_reader_vtn(); } else reader = new mesh_data_reader_vt(); std_stream sstream( &source ); reader->read_stream( sstream ); if ( m_tangents ) { reader->calculate_tangents(); } mesh_raw_channel* channel = new mesh_raw_channel(); nv::uint8* data = nullptr; if ( reader->raw_size() > 0 ) { data = new uint8[ reader->raw_size() ]; std::copy_n( reader->raw_pointer(), reader->raw_size(), data ); } channel->data = data; channel->desc = m_descriptor; channel->count = reader->size * 3; channel->size = reader->raw_size(); delete reader; m_mesh = new mesh_data(); m_mesh->add_channel( channel ); return true; } mesh_data* nv::wavefront_loader::release_mesh_data() { mesh_data* result = m_mesh; m_mesh = nullptr; return result; } nv::wavefront_loader::~wavefront_loader() { if ( m_mesh ) delete m_mesh; }