/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "scenenode.h" #include "Model3d.h" #include "renderer.h" #include "parser.h" #include "Logs.h" #include "sn_utils.h" // stores content of the struct in provided output stream void lighting_data::serialize( std::ostream &Output ) const { sn_utils::s_vec4( Output, diffuse ); sn_utils::s_vec4( Output, ambient ); sn_utils::s_vec4( Output, specular ); } // restores content of the struct from provided input stream void lighting_data::deserialize( std::istream &Input ) { diffuse = sn_utils::d_vec4( Input ); ambient = sn_utils::d_vec4( Input ); specular = sn_utils::d_vec4( Input ); } namespace scene { // stores content of the struct in provided output stream void bounding_area::serialize( std::ostream &Output ) const { // center sn_utils::s_dvec3( Output, center ); // radius sn_utils::ls_float32( Output, radius ); } // restores content of the struct from provided input stream void bounding_area::deserialize( std::istream &Input, bool const Preserveradius ) { center = sn_utils::d_dvec3( Input ); radius = ( Preserveradius ? std::max( radius, sn_utils::ld_float32( Input ) ) : sn_utils::ld_float32( Input ) ); } // sends content of the struct to provided stream void shape_node::shapenode_data::serialize( std::ostream &Output ) const { // bounding area area.serialize( Output ); // visibility sn_utils::ls_float64( Output, rangesquared_min ); sn_utils::ls_float64( Output, rangesquared_max ); sn_utils::s_bool( Output, visible ); // material sn_utils::s_bool( Output, translucent ); // NOTE: material handle is created dynamically on load sn_utils::s_str( Output, ( material != null_handle ? GfxRenderer->Material( material ).name : "" ) ); lighting.serialize( Output ); // geometry sn_utils::s_dvec3( Output, origin ); // NOTE: geometry handle is created dynamically on load // vertex count, followed by vertex data sn_utils::ls_uint32( Output, vertices.size() ); for( auto const &vertex : vertices ) { gfx::basic_vertex( glm::vec3{ vertex.position - origin }, vertex.normal, vertex.texture ) .serialize( Output ); } } // restores content of the struct from provided input stream void shape_node::shapenode_data::deserialize( std::istream &Input ) { // bounding area area.deserialize( Input ); // visibility rangesquared_min = sn_utils::ld_float64( Input ); rangesquared_max = sn_utils::ld_float64( Input ); visible = sn_utils::d_bool( Input ); // material translucent = sn_utils::d_bool( Input ); auto const materialname { sn_utils::d_str( Input ) }; if( false == materialname.empty() ) { material = GfxRenderer->Fetch_Material( materialname ); } lighting.deserialize( Input ); // geometry origin = sn_utils::d_dvec3( Input ); // NOTE: geometry handle is acquired during geometry creation // vertex data vertices.resize( sn_utils::ld_uint32( Input ) ); gfx::basic_vertex localvertex; for( auto &vertex : vertices ) { localvertex.deserialize( Input ); vertex.position = origin + glm::dvec3{ localvertex.position }; vertex.normal = localvertex.normal; vertex.texture = localvertex.texture; } } // sends content of the class to provided stream void shape_node::serialize( std::ostream &Output ) const { // name sn_utils::s_str( Output, m_name ); // node data m_data.serialize( Output ); } // restores content of the node from provided input stream shape_node & shape_node::deserialize( std::istream &Input ) { // name m_name = sn_utils::d_str( Input ); // node data m_data.deserialize( Input ); return *this; } // restores content of the node from provided input stream shape_node & shape_node::import( cParser &Input, scene::node_data const &Nodedata ) { // import common data m_name = Nodedata.name; m_data.rangesquared_min = Nodedata.range_min * Nodedata.range_min; m_data.rangesquared_max = ( Nodedata.range_max >= 0.0 ? Nodedata.range_max * Nodedata.range_max : std::numeric_limits::max() ); std::string token = Input.getToken(); if( token == "material" ) { // lighting settings token = Input.getToken(); while( token != "endmaterial" ) { if( token == "ambient:" ) { Input.getTokens( 3 ); Input >> m_data.lighting.ambient.r >> m_data.lighting.ambient.g >> m_data.lighting.ambient.b; m_data.lighting.ambient /= 255.f; m_data.lighting.ambient.a = 1.f; } else if( token == "diffuse:" ) { Input.getTokens( 3 ); Input >> m_data.lighting.diffuse.r >> m_data.lighting.diffuse.g >> m_data.lighting.diffuse.b; m_data.lighting.diffuse /= 255.f; m_data.lighting.diffuse.a = 1.f; } else if( token == "specular:" ) { Input.getTokens( 3 ); Input >> m_data.lighting.specular.r >> m_data.lighting.specular.g >> m_data.lighting.specular.b; m_data.lighting.specular /= 255.f; m_data.lighting.specular.a = 1.f; } token = Input.getToken(); } token = Input.getToken(); } // assigned material m_data.material = GfxRenderer->Fetch_Material( token ); // determine way to proceed from the assigned diffuse texture // TBT, TODO: add methods to material manager to access these simpler auto const texturehandle = ( m_data.material != null_handle ? GfxRenderer->Material( m_data.material ).textures[0] : null_handle ); auto const &texture = ( texturehandle ? GfxRenderer->Texture( texturehandle ) : opengl_texture() ); // dirty workaround for lack of better api bool const clamps = ( texturehandle ? texture.traits.find( 's' ) != std::string::npos : false ); bool const clampt = ( texturehandle ? texture.traits.find( 't' ) != std::string::npos : false ); // remainder of legacy 'problend' system -- geometry assigned a texture with '@' in its name is treated as translucent, opaque otherwise if( texturehandle != null_handle ) { m_data.translucent = ( ( ( texture.name.find( '@' ) != std::string::npos ) && ( true == texture.has_alpha ) ) ? true : false ); } else { m_data.translucent = false; } // geometry enum subtype { triangles, triangle_strip, triangle_fan }; subtype const nodetype = ( Nodedata.type == "triangles" ? triangles : Nodedata.type == "triangle_strip" ? triangle_strip : triangle_fan ); std::size_t vertexcount{ 0 }; world_vertex vertex, vertex1, vertex2; do { Input.getTokens( 8, false ); Input >> vertex.position.x >> vertex.position.y >> vertex.position.z >> vertex.normal.x >> vertex.normal.y >> vertex.normal.z >> vertex.texture.s >> vertex.texture.t; // clamp texture coordinates if texture wrapping is off if( true == clamps ) { vertex.texture.s = clamp( vertex.texture.s, 0.001f, 0.999f ); } if( true == clampt ) { vertex.texture.t = clamp( vertex.texture.t, 0.001f, 0.999f ); } // convert all data to gl_triangles to allow data merge for matching nodes switch( nodetype ) { case triangles: { if( vertexcount == 0 ) { vertex1 = vertex; } else if( vertexcount == 1 ) { vertex2 = vertex; } else if( vertexcount >= 2 ) { if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { m_data.vertices.emplace_back( vertex1 ); m_data.vertices.emplace_back( vertex2 ); m_data.vertices.emplace_back( vertex ); } else { ErrorLog( "Bad geometry: degenerate triangle encountered" + ( m_name != "" ? " in node \"" + m_name + "\"" : "" ) + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" ); } } ++vertexcount; if( vertexcount > 2 ) { vertexcount = 0; } // start new triangle if needed break; } case triangle_fan: { if( vertexcount == 0 ) { vertex1 = vertex; } else if( vertexcount == 1 ) { vertex2 = vertex; } else if( vertexcount >= 2 ) { if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { m_data.vertices.emplace_back( vertex1 ); m_data.vertices.emplace_back( vertex2 ); m_data.vertices.emplace_back( vertex ); vertex2 = vertex; } else { ErrorLog( "Bad geometry: degenerate triangle encountered" + ( m_name != "" ? " in node \"" + m_name + "\"" : "" ) + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" ); } } ++vertexcount; break; } case triangle_strip: { if( vertexcount == 0 ) { vertex1 = vertex; } else if( vertexcount == 1 ) { vertex2 = vertex; } else if( vertexcount >= 2 ) { if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { // swap order every other triangle, to maintain consistent winding if( vertexcount % 2 == 0 ) { m_data.vertices.emplace_back( vertex1 ); m_data.vertices.emplace_back( vertex2 ); } else { m_data.vertices.emplace_back( vertex2 ); m_data.vertices.emplace_back( vertex1 ); } m_data.vertices.emplace_back( vertex ); vertex1 = vertex2; vertex2 = vertex; } else { ErrorLog( "Bad geometry: degenerate triangle encountered" + ( m_name != "" ? " in node \"" + m_name + "\"" : "" ) + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" ); } } ++vertexcount; break; } default: { break; } } token = Input.getToken(); } while( token != "endtri" ); return *this; } // imports data from provided submodel shape_node & shape_node::convert( TSubModel const *Submodel ) { m_name = Submodel->pName; m_data.lighting.ambient = Submodel->f4Ambient; m_data.lighting.diffuse = Submodel->f4Diffuse; m_data.lighting.specular = Submodel->f4Specular; m_data.material = Submodel->m_material; m_data.translucent = ( true == GfxRenderer->Material( m_data.material ).is_translucent() ); // NOTE: we set unlimited view range typical for terrain, because we don't expect to convert any other 3d models m_data.rangesquared_max = std::numeric_limits::max(); if( Submodel->m_geometry == null_handle ) { return *this; } int vertexcount { 0 }; std::vector importedvertices; world_vertex vertex, vertex1, vertex2; for( auto const &sourcevertex : GfxRenderer->Vertices( Submodel->m_geometry ) ) { vertex.position = sourcevertex.position; vertex.normal = sourcevertex.normal; vertex.texture = sourcevertex.texture; if( vertexcount == 0 ) { vertex1 = vertex; } else if( vertexcount == 1 ) { vertex2 = vertex; } else if( vertexcount >= 2 ) { if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { importedvertices.emplace_back( vertex1 ); importedvertices.emplace_back( vertex2 ); importedvertices.emplace_back( vertex ); } // start a new triangle vertexcount = -1; } ++vertexcount; } if( true == importedvertices.empty() ) { return *this; } // assign imported geometry to the node... m_data.vertices.swap( importedvertices ); // ...and calculate center... for( auto const &vertex : m_data.vertices ) { m_data.area.center += vertex.position; } m_data.area.center /= m_data.vertices.size(); // ...and bounding area double squareradius { 0.0 }; for( auto const &vertex : m_data.vertices ) { squareradius = std::max( squareradius, glm::length2( vertex.position - m_data.area.center ) ); } m_data.area.radius = std::max( m_data.area.radius, static_cast( std::sqrt( squareradius ) ) ); return *this; } // adds content of provided node to already enclosed geometry. returns: true if merge could be performed bool shape_node::merge( shape_node &Shape ) { if( ( m_data.material != Shape.m_data.material ) || ( m_data.lighting != Shape.m_data.lighting ) ) { // can't merge nodes with different appearance return false; } // add geometry from provided node m_data.area.center = interpolate( m_data.area.center, Shape.m_data.area.center, static_cast( Shape.m_data.vertices.size() ) / ( Shape.m_data.vertices.size() + m_data.vertices.size() ) ); m_data.vertices.insert( std::end( m_data.vertices ), std::begin( Shape.m_data.vertices ), std::end( Shape.m_data.vertices ) ); invalidate_radius(); return true; } // generates renderable version of held non-instanced geometry in specified geometry bank void shape_node::create_geometry( gfx::geometrybank_handle const &Bank ) { gfx::vertex_array vertices; vertices.reserve( m_data.vertices.size() ); for( auto const &vertex : m_data.vertices ) { vertices.emplace_back( vertex.position - m_data.origin, vertex.normal, vertex.texture ); } m_data.geometry = GfxRenderer->Insert( vertices, Bank, GL_TRIANGLES ); std::vector().swap( m_data.vertices ); // hipster shrink_to_fit } // calculates shape's bounding radius void shape_node::compute_radius() { auto squaredradius { 0.0 }; for( auto const &vertex : m_data.vertices ) { squaredradius = std::max( squaredradius, glm::length2( vertex.position - m_data.area.center ) ); } m_data.area.radius = static_cast( std::sqrt( squaredradius ) ); } void shape_node::invalidate_radius() { m_data.area.radius = -1.0f; } float shape_node::radius() { if (m_data.area.radius == -1.0f) compute_radius(); return m_data.area.radius; } // sends content of the struct to provided stream void lines_node::linesnode_data::serialize( std::ostream &Output ) const { // bounding area area.serialize( Output ); // visibility sn_utils::ls_float64( Output, rangesquared_min ); sn_utils::ls_float64( Output, rangesquared_max ); sn_utils::s_bool( Output, visible ); // material sn_utils::ls_float32( Output, line_width ); lighting.serialize( Output ); // geometry sn_utils::s_dvec3( Output, origin ); // NOTE: geometry handle is created dynamically on load // vertex count, followed by vertex data sn_utils::ls_uint32( Output, vertices.size() ); for( auto const &vertex : vertices ) { gfx::basic_vertex( glm::vec3{ vertex.position - origin }, vertex.normal, vertex.texture ) .serialize( Output ); } } // restores content of the struct from provided input stream void lines_node::linesnode_data::deserialize( std::istream &Input ) { // bounding area area.deserialize( Input ); // visibility rangesquared_min = sn_utils::ld_float64( Input ); rangesquared_max = sn_utils::ld_float64( Input ); visible = sn_utils::d_bool( Input ); // material line_width = sn_utils::ld_float32( Input ); lighting.deserialize( Input ); // geometry origin = sn_utils::d_dvec3( Input ); // NOTE: geometry handle is acquired during geometry creation // vertex data vertices.resize( sn_utils::ld_uint32( Input ) ); gfx::basic_vertex localvertex; for( auto &vertex : vertices ) { localvertex.deserialize( Input ); vertex.position = origin + glm::dvec3{ localvertex.position }; vertex.normal = localvertex.normal; vertex.texture = localvertex.texture; } } // sends content of the class to provided stream void lines_node::serialize( std::ostream &Output ) const { // name sn_utils::s_str( Output, m_name ); // node data m_data.serialize( Output ); } // restores content of the node from provided input stream lines_node & lines_node::deserialize( std::istream &Input ) { // name m_name = sn_utils::d_str( Input ); // node data m_data.deserialize( Input ); return *this; } // restores content of the node from provded input stream lines_node & lines_node::import( cParser &Input, scene::node_data const &Nodedata ) { // import common data m_name = Nodedata.name; m_data.rangesquared_min = Nodedata.range_min * Nodedata.range_min; m_data.rangesquared_max = ( Nodedata.range_max >= 0.0 ? Nodedata.range_max * Nodedata.range_max : std::numeric_limits::max() ); // material Input.getTokens( 3, false ); Input >> m_data.lighting.diffuse.r >> m_data.lighting.diffuse.g >> m_data.lighting.diffuse.b; m_data.lighting.diffuse /= 255.f; m_data.lighting.diffuse.a = 1.f; Input.getTokens( 1, false ); Input >> m_data.line_width; m_data.line_width = std::min( 30.f, m_data.line_width ); // 30 pix equals rougly width of a signal pole viewed from ~1m away // geometry enum subtype { lines, line_strip, line_loop }; subtype const nodetype = ( Nodedata.type == "lines" ? lines : Nodedata.type == "line_strip" ? line_strip : line_loop ); std::size_t vertexcount { 0 }; world_vertex vertex, vertex0, vertex1; std::string token = Input.getToken(); do { vertex.position.x = std::atof( token.c_str() ); Input.getTokens( 2, false ); Input >> vertex.position.y >> vertex.position.z; // convert all data to gl_lines to allow data merge for matching nodes switch( nodetype ) { case lines: { m_data.vertices.emplace_back( vertex ); break; } case line_strip: { if( vertexcount > 0 ) { m_data.vertices.emplace_back( vertex1 ); m_data.vertices.emplace_back( vertex ); } vertex1 = vertex; ++vertexcount; break; } case line_loop: { if( vertexcount == 0 ) { vertex0 = vertex; vertex1 = vertex; } else { m_data.vertices.emplace_back( vertex1 ); m_data.vertices.emplace_back( vertex ); } vertex1 = vertex; ++vertexcount; break; } default: { break; } } token = Input.getToken(); } while( token != "endline" ); // add closing line for the loop if( ( nodetype == line_loop ) && ( vertexcount > 2 ) ) { m_data.vertices.emplace_back( vertex1 ); m_data.vertices.emplace_back( vertex0 ); } if( m_data.vertices.size() % 2 != 0 ) { ErrorLog( "Lines node specified odd number of vertices, defined in file \"" + Input.Name() + "\" (line " + std::to_string( Input.Line() - 1 ) + ")" ); m_data.vertices.pop_back(); } return *this; } // adds content of provided node to already enclosed geometry. returns: true if merge could be performed bool lines_node::merge( lines_node &Lines ) { if( ( m_data.line_width != Lines.m_data.line_width ) || ( m_data.lighting != Lines.m_data.lighting ) ) { // can't merge nodes with different appearance return false; } // add geometry from provided node m_data.area.center = interpolate( m_data.area.center, Lines.m_data.area.center, static_cast( Lines.m_data.vertices.size() ) / ( Lines.m_data.vertices.size() + m_data.vertices.size() ) ); m_data.vertices.insert( std::end( m_data.vertices ), std::begin( Lines.m_data.vertices ), std::end( Lines.m_data.vertices ) ); // NOTE: we could recalculate radius with something other than brute force, but it'll do compute_radius(); return true; } // generates renderable version of held non-instanced geometry in specified geometry bank void lines_node::create_geometry( gfx::geometrybank_handle const &Bank ) { gfx::vertex_array vertices; vertices.reserve( m_data.vertices.size() ); for( auto const &vertex : m_data.vertices ) { vertices.emplace_back( vertex.position - m_data.origin, vertex.normal, vertex.texture ); } m_data.geometry = GfxRenderer->Insert( vertices, Bank, GL_LINES ); std::vector().swap( m_data.vertices ); // hipster shrink_to_fit } // calculates node's bounding radius void lines_node::compute_radius() { auto squaredradius { 0.0 }; for( auto const &vertex : m_data.vertices ) { squaredradius = std::max( squaredradius, glm::length2( vertex.position - m_data.area.center ) ); } m_data.area.radius = static_cast( std::sqrt( squaredradius ) ); } /* memory_node & memory_node::deserialize( cParser &Input, node_data const &Nodedata ) { // import common data m_name = Nodedata.name; Input.getTokens( 3 ); Input >> m_data.area.center.x >> m_data.area.center.y >> m_data.area.center.z; TMemCell memorycell( Nodedata.name ); memorycell.Load( &Input ); } */ basic_node::basic_node( scene::node_data const &Nodedata ) : m_name( Nodedata.name ) { m_rangesquaredmin = Nodedata.range_min * Nodedata.range_min; m_rangesquaredmax = ( Nodedata.range_max >= 0.0 ? Nodedata.range_max * Nodedata.range_max : std::numeric_limits::max() ); } // sends content of the class to provided stream void basic_node::serialize( std::ostream &Output ) const { // bounding area m_area.serialize( Output ); // visibility sn_utils::ls_float64( Output, m_rangesquaredmin ); sn_utils::ls_float64( Output, m_rangesquaredmax ); sn_utils::s_bool( Output, m_visible ); // name sn_utils::s_str( Output, m_name ); // template method implementation serialize_( Output ); } // restores content of the class from provided stream void basic_node::deserialize( std::istream &Input ) { // bounding area m_area.deserialize( Input ); // visibility m_rangesquaredmin = sn_utils::ld_float64( Input ); m_rangesquaredmax = sn_utils::ld_float64( Input ); m_visible = sn_utils::d_bool( Input ); // name m_name = sn_utils::d_str( Input ); // template method implementation deserialize_( Input ); } // sends basic content of the class in legacy (text) format to provided stream void basic_node::export_as_text( std::ostream &Output ) const { Output // header << "node" // visibility << ' ' << ( m_rangesquaredmax < std::numeric_limits::max() ? std::sqrt( m_rangesquaredmax ) : -1 ) << ' ' << std::sqrt( m_rangesquaredmin ) // name << ' ' << m_name << ' '; // template method implementation export_as_text_( Output ); } void basic_node::export_as_text( std::string &Output ) const { std::stringstream converter; export_as_text( converter ); Output += converter.str(); } float const & basic_node::radius() { if( m_area.radius == -1.0 ) { // calculate if needed m_area.radius = radius_(); } return m_area.radius; } // radius() subclass details, calculates node's bounding radius // by default nodes are 'virtual don't extend from their center point float basic_node::radius_() { return 0.f; } } // scene //---------------------------------------------------------------------------