/* 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.h" #include "renderer.h" #include "logs.h" namespace scene { // adds provided shape to the cell void basic_cell::insert( shape_node Shape ) { m_active = true; auto const &shapedata { Shape.data() }; auto &shapes = ( shapedata.translucent ? m_shapestranslucent : m_shapesopaque ); for( auto &targetshape : shapes ) { // try to merge shapes with matching view ranges... auto const &targetshapedata { targetshape.data() }; if( ( shapedata.rangesquared_min == targetshapedata.rangesquared_min ) && ( shapedata.rangesquared_max == targetshapedata.rangesquared_max ) // ...and located close to each other (within arbitrary limit of 25m) && ( glm::length( shapedata.area.center - targetshapedata.area.center ) < 25.0 ) ) { if( true == targetshape.merge( Shape ) ) { // if the shape was merged there's nothing left to do return; } } } // otherwise add the shape to the relevant list Shape.origin( m_area.center ); shapes.emplace_back( Shape ); } // adds provided path to the cell void basic_cell::insert( TTrack *Path ) { m_active = true; // TODO: add animation hook Path->origin( m_area.center ); m_paths.emplace_back( Path ); } // adds provided traction piece to the cell void basic_cell::insert( TTraction *Traction ) { m_active = true; Traction->origin( m_area.center ); m_traction.emplace_back( Traction ); } // adds provided model instance to the cell void basic_cell::insert( TAnimModel *Instance ) { m_active = true; auto const flags = Instance->Flags(); auto alpha = ( Instance->Material() != nullptr ? Instance->Material()->textures_alpha : 0x30300030 ); // assign model to appropriate render phases if( alpha & flags & 0x2F2F002F ) { // translucent pieces m_instancetranslucent.emplace_back( Instance ); } alpha ^= 0x0F0F000F; // odwrócenie flag tekstur, aby wyłapać nieprzezroczyste if( alpha & flags & 0x1F1F001F ) { // opaque pieces m_instancesopaque.emplace_back( Instance ); } } // generates renderable version of held non-instanced geometry void basic_cell::create_geometry( geometrybank_handle const &Bank ) { if( false == m_active ) { return; } // nothing to do here for( auto &shape : m_shapesopaque ) { shape.create_geometry( Bank ); } for( auto &shape : m_shapestranslucent ) { shape.create_geometry( Bank ); } #ifndef EU07_USE_OLD_GROUNDCODE for( auto *path : m_paths ) { path->create_geometry( Bank ); } for( auto *traction : m_traction ) { traction->create_geometry( Bank ); } #endif } // sets center point of the section void basic_cell::center( glm::dvec3 Center ) { m_area.center = Center; // NOTE: we should also update origin point for the contained nodes, but in practice we can skip this // as all nodes will be added only after the proper center point was set, and won't change } // adds provided shape to the section void basic_section::insert( shape_node Shape ) { auto const &shapedata = Shape.data(); if( ( true == shapedata.translucent ) || ( shapedata.rangesquared_max <= 90000.0 ) || ( shapedata.rangesquared_min > 0.0 ) ) { // small, translucent or not always visible shapes are placed in the sub-cells cell( shapedata.area.center ).insert( Shape ); } else { // large, opaque shapes are placed on section level for( auto &shape : m_shapes ) { // check first if the shape can't be merged with one of the shapes already present in the section if( true == shape.merge( Shape ) ) { // if the shape was merged there's nothing left to do return; } } // otherwise add the shape to the section's list Shape.origin( m_area.center ); m_shapes.emplace_back( Shape ); } } // adds provided path to the section void basic_section::insert( TTrack *Path ) { // pass the node to the appropriate partitioning cell // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done cell( Path->location() ).insert( Path ); } // adds provided path to the section void basic_section::insert( TTraction *Traction ) { // pass the node to the appropriate partitioning cell // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done cell( Traction->location() ).insert( Traction ); } // adds provided model instance to the section void basic_section::insert( TAnimModel *Instance ) { // pass the node to the appropriate partitioning cell // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done cell( Instance->location() ).insert( Instance ); } // sets center point of the section void basic_section::center( glm::dvec3 Center ) { m_area.center = Center; // set accordingly center points of the section's partitioning cells // NOTE: we should also update origin point for the contained nodes, but in practice we can skip this // as all nodes will be added only after the proper center point was set, and won't change auto const centeroffset = -( EU07_SECTIONSIZE / EU07_CELLSIZE / 2 * EU07_CELLSIZE ) + EU07_CELLSIZE / 2; glm::dvec3 sectioncornercenter { m_area.center + glm::dvec3{ centeroffset, 0, centeroffset } }; auto row { 0 }, column { 0 }; for( auto &cell : m_cells ) { cell.center( sectioncornercenter + glm::dvec3{ column * EU07_CELLSIZE, 0.0, row * EU07_CELLSIZE } ); if( ++column >= EU07_SECTIONSIZE / EU07_CELLSIZE ) { ++row; column = 0; } } } // generates renderable version of held non-instanced geometry void basic_section::create_geometry() { if( true == m_geometrycreated ) { return; } else { // mark it done for future checks m_geometrycreated = true; } // since sections can be empty, we're doing lazy initialization of the geometry bank, when something may actually use it if( m_geometrybank == null_handle ) { m_geometrybank = GfxRenderer.Create_Bank(); } for( auto &shape : m_shapes ) { shape.create_geometry( m_geometrybank ); } for( auto &cell : m_cells ) { cell.create_geometry( m_geometrybank ); } } // provides access to section enclosing specified point basic_cell & basic_section::cell( glm::dvec3 const &Location ) { auto const column = static_cast( std::floor( ( Location.x - ( m_area.center.x - EU07_SECTIONSIZE / 2 ) ) / EU07_CELLSIZE ) ); auto const row = static_cast( std::floor( ( Location.z - ( m_area.center.z - EU07_SECTIONSIZE / 2 ) ) / EU07_CELLSIZE ) ); return m_cells[ clamp( row, 0, ( EU07_SECTIONSIZE / EU07_CELLSIZE ) - 1 ) * ( EU07_SECTIONSIZE / EU07_CELLSIZE ) + clamp( column, 0, ( EU07_SECTIONSIZE / EU07_CELLSIZE ) - 1 ) ] ; } basic_region::basic_region() { m_sections.fill( nullptr ); /* // initialize centers of sections: // calculate center of 'top left' region section... auto const centeroffset = -( EU07_REGIONSIDESECTIONCOUNT / 2 * EU07_SECTIONSIZE ) + EU07_SECTIONSIZE / 2; glm::dvec3 regioncornercenter { centeroffset, 0, centeroffset }; auto row { 0 }, column { 0 }; // ...move through section array assigning centers left to right, front/top to back/bottom for( auto §ion : m_sections ) { section.center( regioncornercenter + glm::dvec3{ column * EU07_SECTIONSIZE, 0.0, row * EU07_SECTIONSIZE } ); if( ++column >= EU07_REGIONSIDESECTIONCOUNT ) { ++row; column = 0; } } */ } basic_region::~basic_region() { for( auto section : m_sections ) { if( section != nullptr ) { delete section; } } } void basic_region::insert_shape( shape_node Shape, scratch_data &Scratchpad ) { // shape might need to be split into smaller pieces, so we create list of nodes instead of just single one // using deque so we can do single pass iterating and addding generated pieces without invalidating anything std::deque shapes { Shape }; auto &shape = shapes.front(); if( shape.m_data.vertices.empty() ) { return; } // adjust input if necessary: if( Scratchpad.location_rotation != glm::vec3( 0, 0, 0 ) ) { // rotate... auto const rotation = glm::radians( Scratchpad.location_rotation ); for( auto &vertex : shape.m_data.vertices ) { vertex.position = glm::rotateZ( vertex.position, rotation.z ); vertex.position = glm::rotateX( vertex.position, rotation.x ); vertex.position = glm::rotateY( vertex.position, rotation.y ); vertex.normal = glm::rotateZ( vertex.normal, rotation.z ); vertex.normal = glm::rotateX( vertex.normal, rotation.x ); vertex.normal = glm::rotateY( vertex.normal, rotation.y ); } } if( ( false == Scratchpad.location_offset.empty() ) && ( Scratchpad.location_offset.top() != glm::dvec3( 0, 0, 0 ) ) ) { // ...and move auto const offset = Scratchpad.location_offset.top(); for( auto &vertex : shape.m_data.vertices ) { vertex.position += offset; } } // calculate bounding area for( auto const &vertex : shape.m_data.vertices ) { shape.m_data.area.center += vertex.position; } shape.m_data.area.center /= shape.m_data.vertices.size(); // trim the shape if needed. trimmed parts will be added to list as separate nodes for( std::size_t index = 0; index < shapes.size(); ++index ) { while( true == RaTriangleDivider( shapes[ index ], shapes ) ) { ; // all work is done during expression check } // with the trimming done we can calculate shape's bounding radius shape.compute_radius(); } // move the data into appropriate section(s) for( auto &shape : shapes ) { if( point_inside( shape.m_data.area.center ) ) { // NOTE: nodes placed outside of region boundaries are discarded section( shape.m_data.area.center ).insert( shape ); } else { ErrorLog( "Bad scenario: shape node" + ( shape.m_name.empty() ? "" : " \"" + shape.m_name + "\"" ) + " placed in location outside region bounds (" + to_string( shape.m_data.area.center ) + ")" ); } } } // inserts provided track in the region void basic_region::insert_path( TTrack *Path, scratch_data &Scratchpad ) { // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done auto center = Path->location(); if( point_inside( center ) ) { // NOTE: nodes placed outside of region boundaries are discarded section( center ).insert( Path ); } else { // tracks are guaranteed to hava a name so we can skip the check ErrorLog( "Bad scenario: track node \"" + Path->name() + "\" placed in location outside region bounds (" + to_string( center ) + ")" ); } } // inserts provided track in the region void basic_region::insert_traction( TTraction *Traction, scratch_data &Scratchpad ) { // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done auto center = Traction->location(); if( point_inside( center ) ) { // NOTE: nodes placed outside of region boundaries are discarded section( center ).insert( Traction ); } else { // tracks are guaranteed to hava a name so we can skip the check ErrorLog( "Bad scenario: traction node \"" + Traction->name() + "\" placed in location outside region bounds (" + to_string( center ) + ")" ); } } // inserts provided instance of 3d model in the region void basic_region::insert_instance( TAnimModel *Instance, scratch_data &Scratchpad ) { // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done auto center = Instance->location(); if( point_inside( center ) ) { // NOTE: nodes placed outside of region boundaries are discarded section( center ).insert( Instance ); } else { // tracks are guaranteed to hava a name so we can skip the check ErrorLog( "Bad scenario: model node \"" + Instance->name() + "\" placed in location outside region bounds (" + to_string( center ) + ")" ); } } // checks whether specified point is within boundaries of the region bool basic_region::point_inside( glm::dvec3 const &Location ) { double const regionboundary = EU07_REGIONSIDESECTIONCOUNT / 2 * EU07_SECTIONSIZE; return ( ( Location.x > -regionboundary ) && ( Location.x < regionboundary ) && ( Location.z > -regionboundary ) && ( Location.z < regionboundary ) ); } // trims provided shape to fit into a section, adds trimmed part at the end of provided list // NOTE: legacy function. TBD, TODO: clean it up? bool basic_region::RaTriangleDivider( shape_node &Shape, std::deque &Shapes ) { if( Shape.m_data.vertices.size() != 3 ) { // tylko gdy jeden trójkąt return false; } auto const margin { 200.0 }; auto x0 = EU07_SECTIONSIZE * std::floor( 0.001 * Shape.m_data.area.center.x ) - margin; auto x1 = x0 + EU07_SECTIONSIZE + margin * 2; auto z0 = EU07_SECTIONSIZE * std::floor( 0.001 * Shape.m_data.area.center.z ) - margin; auto z1 = z0 + EU07_SECTIONSIZE + margin * 2; if( ( Shape.m_data.vertices[ 0 ].position.x >= x0 ) && ( Shape.m_data.vertices[ 0 ].position.x <= x1 ) && ( Shape.m_data.vertices[ 0 ].position.z >= z0 ) && ( Shape.m_data.vertices[ 0 ].position.z <= z1 ) && ( Shape.m_data.vertices[ 1 ].position.x >= x0 ) && ( Shape.m_data.vertices[ 1 ].position.x <= x1 ) && ( Shape.m_data.vertices[ 1 ].position.z >= z0 ) && ( Shape.m_data.vertices[ 1 ].position.z <= z1 ) && ( Shape.m_data.vertices[ 2 ].position.x >= x0 ) && ( Shape.m_data.vertices[ 2 ].position.x <= x1 ) && ( Shape.m_data.vertices[ 2 ].position.z >= z0 ) && ( Shape.m_data.vertices[ 2 ].position.z <= z1 ) ) { // trójkąt wystający mniej niż 200m z kw. kilometrowego jest do przyjęcia return false; } // Ra: przerobić na dzielenie na 2 trójkąty, podział w przecięciu z siatką kilometrową // Ra: i z rekurencją będzie dzielić trzy trójkąty, jeśli będzie taka potrzeba int divide { -1 }; // bok do podzielenia: 0=AB, 1=BC, 2=CA; +4=podział po OZ; +8 na x1/z1 double min { 0.0 }, mul; // jeśli przechodzi przez oś, iloczyn będzie ujemny x0 += margin; x1 -= margin; // przestawienie na siatkę z0 += margin; z1 -= margin; // AB na wschodzie mul = ( Shape.m_data.vertices[ 0 ].position.x - x0 ) * ( Shape.m_data.vertices[ 1 ].position.x - x0 ); if( mul < min ) { min = mul; divide = 0; } // BC na wschodzie mul = ( Shape.m_data.vertices[ 1 ].position.x - x0 ) * ( Shape.m_data.vertices[ 2 ].position.x - x0 ); if( mul < min ) { min = mul; divide = 1; } // CA na wschodzie mul = ( Shape.m_data.vertices[ 2 ].position.x - x0 ) * ( Shape.m_data.vertices[ 0 ].position.x - x0 ); if( mul < min ) { min = mul; divide = 2; } // AB na zachodzie mul = ( Shape.m_data.vertices[ 0 ].position.x - x1 ) * ( Shape.m_data.vertices[ 1 ].position.x - x1 ); if( mul < min ) { min = mul; divide = 8; } // BC na zachodzie mul = ( Shape.m_data.vertices[ 1 ].position.x - x1 ) * ( Shape.m_data.vertices[ 2 ].position.x - x1 ); if( mul < min ) { min = mul; divide = 9; } // CA na zachodzie mul = ( Shape.m_data.vertices[ 2 ].position.x - x1 ) * ( Shape.m_data.vertices[ 0 ].position.x - x1 ); if( mul < min ) { min = mul; divide = 10; } // AB na południu mul = ( Shape.m_data.vertices[ 0 ].position.z - z0 ) * ( Shape.m_data.vertices[ 1 ].position.z - z0 ); if( mul < min ) { min = mul; divide = 4; } // BC na południu mul = ( Shape.m_data.vertices[ 1 ].position.z - z0 ) * ( Shape.m_data.vertices[ 2 ].position.z - z0 ); if( mul < min ) { min = mul; divide = 5; } // CA na południu mul = ( Shape.m_data.vertices[ 2 ].position.z - z0 ) * ( Shape.m_data.vertices[ 0 ].position.z - z0 ); if( mul < min ) { min = mul; divide = 6; } // AB na północy mul = ( Shape.m_data.vertices[ 0 ].position.z - z1 ) * ( Shape.m_data.vertices[ 1 ].position.z - z1 ); if( mul < min ) { min = mul; divide = 12; } // BC na północy mul = ( Shape.m_data.vertices[ 1 ].position.z - z1 ) * ( Shape.m_data.vertices[ 2 ].position.z - z1 ); if( mul < min ) { min = mul; divide = 13; } // CA na północy mul = (Shape.m_data.vertices[2].position.z - z1) * (Shape.m_data.vertices[0].position.z - z1); if( mul < min ) { divide = 14; } // tworzymy jeden dodatkowy trójkąt, dzieląc jeden bok na przecięciu siatki kilometrowej Shapes.emplace_back( Shape ); // copy current shape auto &newshape = Shapes.back(); switch (divide & 3) { // podzielenie jednego z boków, powstaje wierzchołek D case 0: { // podział AB (0-1) -> ADC i DBC newshape.m_data.vertices[ 2 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek C jest wspólny newshape.m_data.vertices[ 1 ] = Shape.m_data.vertices[ 1 ]; // wierzchołek B przechodzi do nowego if( divide & 4 ) { Shape.m_data.vertices[ 1 ].set_from_z( Shape.m_data.vertices[ 0 ], Shape.m_data.vertices[ 1 ], ( ( divide & 8 ) ? z1 : z0 ) ); } else { Shape.m_data.vertices[ 1 ].set_from_x( Shape.m_data.vertices[ 0 ], Shape.m_data.vertices[ 1 ], ( ( divide & 8 ) ? x1 : x0 ) ); } newshape.m_data.vertices[ 0 ] = Shape.m_data.vertices[ 1 ]; // wierzchołek D jest wspólny break; } case 1: { // podział BC (1-2) -> ABD i ADC newshape.m_data.vertices[ 0 ] = Shape.m_data.vertices[ 0 ]; // wierzchołek A jest wspólny newshape.m_data.vertices[ 2 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek C przechodzi do nowego if( divide & 4 ) { Shape.m_data.vertices[ 2 ].set_from_z( Shape.m_data.vertices[ 1 ], Shape.m_data.vertices[ 2 ], ( ( divide & 8 ) ? z1 : z0 ) ); } else { Shape.m_data.vertices[ 2 ].set_from_x( Shape.m_data.vertices[ 1 ], Shape.m_data.vertices[ 2 ], ( ( divide & 8 ) ? x1 : x0 ) ); } newshape.m_data.vertices[ 1 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek D jest wspólny break; } case 2: { // podział CA (2-0) -> ABD i DBC newshape.m_data.vertices[ 1 ] = Shape.m_data.vertices[ 1 ]; // wierzchołek B jest wspólny newshape.m_data.vertices[ 2 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek C przechodzi do nowego if( divide & 4 ) { Shape.m_data.vertices[ 2 ].set_from_z( Shape.m_data.vertices[ 2 ], Shape.m_data.vertices[ 0 ], ( ( divide & 8 ) ? z1 : z0 ) ); } else { Shape.m_data.vertices[ 2 ].set_from_x( Shape.m_data.vertices[ 2 ], Shape.m_data.vertices[ 0 ], ( ( divide & 8 ) ? x1 : x0 ) ); } newshape.m_data.vertices[ 0 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek D jest wspólny break; } } // przeliczenie środków ciężkości obu Shape.m_data.area.center = ( Shape.m_data.vertices[ 0 ].position + Shape.m_data.vertices[ 1 ].position + Shape.m_data.vertices[ 2 ].position ) / 3.0; newshape.m_data.area.center = ( newshape.m_data.vertices[ 0 ].position + newshape.m_data.vertices[ 1 ].position + newshape.m_data.vertices[ 2 ].position ) / 3.0; return true; } // provides access to section enclosing specified point basic_section & basic_region::section( glm::dvec3 const &Location ) { auto const column = static_cast( std::floor( Location.x / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ); auto const row = static_cast( std::floor( Location.z / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ); auto §ion = m_sections[ clamp( row, 0, EU07_REGIONSIDESECTIONCOUNT - 1 ) * EU07_REGIONSIDESECTIONCOUNT + clamp( column, 0, EU07_REGIONSIDESECTIONCOUNT - 1 ) ] ; if( section == nullptr ) { // there's no guarantee the section exists at this point, so check and if needed, create it section = new basic_section(); // assign center of the section auto const centeroffset = -( EU07_REGIONSIDESECTIONCOUNT / 2 * EU07_SECTIONSIZE ) + EU07_SECTIONSIZE / 2; glm::dvec3 regioncornercenter { centeroffset, 0, centeroffset }; section->center( regioncornercenter + glm::dvec3{ column * EU07_SECTIONSIZE, 0.0, row * EU07_SECTIONSIZE } ); } return *section; } } // scene //---------------------------------------------------------------------------