16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-18 00:49:19 +02:00
Files
maszyna/scene/scenenode.cpp
2026-05-19 19:04:46 +02:00

910 lines
28 KiB
C++

/*
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 "scene/scenenode.h"
#include "model/Model3d.h"
#include "rendering/renderer.h"
#include "utilities/parser.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "scene/sn_utils.h"
namespace
{
constexpr char const *geometry_token_break = "\n\r\t ;";
enum class shape_import_subtype
{
triangles,
triangle_strip,
triangle_fan
};
bool read_shape_vertex_fast(cParser &Input, world_vertex &vertex)
{
return Input.readTokenDouble(vertex.position.x, false, geometry_token_break)
&& Input.readTokenDouble(vertex.position.y, false, geometry_token_break)
&& Input.readTokenDouble(vertex.position.z, false, geometry_token_break)
&& Input.readTokenFloat(vertex.normal.x, false, geometry_token_break)
&& Input.readTokenFloat(vertex.normal.y, false, geometry_token_break)
&& Input.readTokenFloat(vertex.normal.z, false, geometry_token_break)
&& Input.readTokenFloat(vertex.texture.s, false, geometry_token_break)
&& Input.readTokenFloat(vertex.texture.t, false, geometry_token_break);
}
void process_shape_imported_vertex(scene::shape_node::shapenode_data &data,
shape_import_subtype const nodetype,
world_vertex &vertex,
world_vertex &vertex1,
world_vertex &vertex2,
std::size_t &vertexcount,
bool const clamps,
bool const clampt,
std::string const &node_name)
{
if (true == clamps)
{
vertex.texture.s = std::clamp(vertex.texture.s, 0.001f, 0.999f);
}
if (true == clampt)
{
vertex.texture.t = std::clamp(vertex.texture.t, 0.001f, 0.999f);
}
switch (nodetype)
{
case shape_import_subtype::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))
{
data.vertices.emplace_back(vertex1);
data.vertices.emplace_back(vertex2);
data.vertices.emplace_back(vertex);
}
else
{
ErrorLog(
"Bad geometry: degenerate triangle encountered"
+ (node_name != "" ? " in node \"" + node_name + "\"" : "")
+ " (vertices: " + to_string(vertex1.position) + " + " + to_string(vertex2.position) + " + " + to_string(vertex.position) + ")");
}
}
++vertexcount;
if (vertexcount > 2)
{
vertexcount = 0;
}
break;
}
case shape_import_subtype::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))
{
data.vertices.emplace_back(vertex1);
data.vertices.emplace_back(vertex2);
data.vertices.emplace_back(vertex);
vertex2 = vertex;
}
else
{
ErrorLog(
"Bad geometry: degenerate triangle encountered"
+ (node_name != "" ? " in node \"" + node_name + "\"" : "")
+ " (vertices: " + to_string(vertex1.position) + " + " + to_string(vertex2.position) + " + " + to_string(vertex.position) + ")");
}
}
++vertexcount;
break;
}
case shape_import_subtype::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))
{
if (vertexcount % 2 == 0)
{
data.vertices.emplace_back(vertex1);
data.vertices.emplace_back(vertex2);
}
else
{
data.vertices.emplace_back(vertex2);
data.vertices.emplace_back(vertex1);
}
data.vertices.emplace_back(vertex);
vertex1 = vertex2;
vertex2 = vertex;
}
else
{
ErrorLog(
"Bad geometry: degenerate triangle encountered"
+ (node_name != "" ? " in node \"" + node_name + "\"" : "")
+ " (vertices: " + to_string(vertex1.position) + " + " + to_string(vertex2.position) + " + " + to_string(vertex.position) + ")");
}
}
++vertexcount;
break;
}
}
}
} // namespace
// 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 );
bool has_userdata = !userdata.empty();
// 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 );
sn_utils::s_bool( Output, has_userdata );
// NOTE: material handle is created dynamically on load
sn_utils::s_str(
Output,
( material != null_handle ?
GfxRenderer->Material( material )->GetName() :
"" ) );
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( int i = 0; i < vertices.size(); ++i ) {
gfx::basic_vertex::convert(vertices[i], origin)
.serialize( Output, false );
if(has_userdata){
userdata[i].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 );
bool has_userdata = 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 ) );
if(has_userdata)
userdata.resize(vertices.size());
gfx::basic_vertex localvertex;
for( int i = 0; i < vertices.size(); ++i ) {
localvertex.deserialize( Input, false );
vertices[i] = localvertex.to_world(origin);
if(has_userdata)
userdata[i].deserialize( Input );
}
}
// 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<double>::max() );
std::string token = Input.getToken<std::string>();
if( token == "material" ) {
// lighting settings
token = Input.getToken<std::string>();
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<std::string>();
}
token = Input.getToken<std::string>();
}
// assigned material
replace_slashes(token);
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 )->GetTexture(0) :
null_handle );
auto const &texture = (
texturehandle ?
GfxRenderer->Texture( texturehandle ) :
*ITexture::null_texture() ); // dirty workaround for lack of better api
bool const clamps = (
texturehandle ?
contains( texture.get_traits(), 's' ) :
false );
bool const clampt = (
texturehandle ?
contains( texture.get_traits(), 't' ) :
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 = (
( ( contains( texture.get_name(), '@' ) )
&& ( true == texture.get_has_alpha() ) ) ?
true :
false );
}
else {
m_data.translucent = false;
}
// geometry
shape_import_subtype const nodetype = (
Nodedata.type == "triangles" ? shape_import_subtype::triangles :
Nodedata.type == "triangle_strip" ? shape_import_subtype::triangle_strip :
shape_import_subtype::triangle_fan );
std::size_t vertexcount{ 0 };
world_vertex vertex, vertex1, vertex2;
if (Global.ScenarioParserFastGeometry)
{
do
{
if (false == read_shape_vertex_fast(Input, vertex))
{
break;
}
process_shape_imported_vertex(m_data, nodetype, vertex, vertex1, vertex2, vertexcount, clamps, clampt, m_name);
Input.readNextToken(token, false, geometry_token_break);
} while (token != "endtri");
}
else
{
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;
process_shape_imported_vertex(m_data, nodetype, vertex, vertex1, vertex2, vertexcount, clamps, clampt, m_name);
token = Input.getToken<std::string>();
} 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 = ( GfxRenderer->Material( m_data.material )->get_or_guess_opacity() == 0.0f );
// 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<double>::max();
if( Submodel->m_geometry.handle == null_handle ) { return *this; }
int vertexcount { 0 };
std::vector<world_vertex> importedvertices;
gfx::userdata_array importeduserdata;
if(!GfxRenderer->Indices(Submodel->m_geometry.handle).empty()){
const auto& vertices = GfxRenderer->Vertices(Submodel->m_geometry.handle);
const auto& userdatas = GfxRenderer->UserData(Submodel->m_geometry.handle);
bool has_userdata = !userdatas.empty();
world_vertex vertex;
for(const auto index : GfxRenderer->Indices(Submodel->m_geometry.handle)){
vertex = vertices[index].to_world();
importedvertices.emplace_back(vertex);
if (has_userdata)
importeduserdata.emplace_back(userdatas[index]);
}
}
else{
world_vertex vertex, vertex1, vertex2;
gfx::vertex_userdata userdata, userdata1, userdata2;
const auto& vertices = GfxRenderer->Vertices(Submodel->m_geometry.handle);
const auto& userdatas = GfxRenderer->UserData(Submodel->m_geometry.handle);
bool has_userdata = !userdatas.empty();
for( int i = 0; i < vertices.size(); ++i ) {
vertex = vertices[i].to_world();
if( has_userdata ) userdata = userdatas[i];
if( vertexcount == 0 ) { vertex1 = vertex; userdata1 = userdata; }
else if( vertexcount == 1 ) { vertex2 = vertex; userdata2 = userdata; }
else if( vertexcount >= 2 ) {
if( !degenerate( vertex1.position, vertex2.position, vertex.position ) ) {
importedvertices.emplace_back( vertex1 );
importedvertices.emplace_back( vertex2 );
importedvertices.emplace_back( vertex );
if( has_userdata ) {
importeduserdata.emplace_back( userdata1 );
importeduserdata.emplace_back( userdata2 );
importeduserdata.emplace_back( userdata );
}
}
// start a new triangle
vertexcount = -1;
}
++vertexcount;
}
}
if( true == importedvertices.empty() ) { return *this; }
// assign imported geometry to the node...
m_data.vertices.swap( importedvertices );
m_data.userdata.swap( importeduserdata );
// ...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<float>( 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 =
glm::mix(
m_data.area.center, Shape.m_data.area.center,
static_cast<double>( 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(gfx::basic_vertex::convert(vertex, m_data.origin));
}
m_data.geometry = GfxRenderer->Insert(vertices, m_data.userdata, Bank, GL_TRIANGLES);
std::vector<world_vertex>().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<float>( 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<double>::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<std::string>();
auto process_line_vertex = [&]()
{
// 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; }
}
};
if (Global.ScenarioParserFastGeometry)
{
do
{
char *end = nullptr;
vertex.position.x = std::strtod(token.c_str(), &end);
if (false == Input.readTokenDouble(vertex.position.y, false, geometry_token_break)
|| false == Input.readTokenDouble(vertex.position.z, false, geometry_token_break))
{
break;
}
process_line_vertex();
Input.readNextToken(token, false, geometry_token_break);
} while (token != "endline");
}
else
{
do
{
vertex.position.x = std::atof(token.c_str());
Input.getTokens(2, false);
Input >> vertex.position.y >> vertex.position.z;
process_line_vertex();
token = Input.getToken<std::string>();
} 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 =
glm::mix(
m_data.area.center, Lines.m_data.area.center,
static_cast<double>( 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, m_data.userdata, Bank, GL_LINES );
std::vector<world_vertex>().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<float>( 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 )
{
uuid = UID::random();
node_type = Nodedata.type;
m_rangesquaredmin = Nodedata.range_min * Nodedata.range_min;
m_rangesquaredmax = (
Nodedata.range_max >= 0.0 ?
Nodedata.range_max * Nodedata.range_max :
std::numeric_limits<double>::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<double>::max() ? std::sqrt( m_rangesquaredmax ) : -1 )
<< ' ' << std::sqrt( m_rangesquaredmin )
// name
<< ' ' << ( m_name.empty() ? "none" : 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
//---------------------------------------------------------------------------