Files
maszyna/scene.cpp
2018-02-16 15:33:11 +01:00

1555 lines
60 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.h"
#include "simulation.h"
#include "Globals.h"
#include "Timer.h"
#include "Logs.h"
#include "sn_utils.h"
#include "renderer.h"
namespace scene {
std::string const EU07_FILEEXTENSION_REGION { ".sbt" };
// legacy method, finds and assigns traction piece to specified pantograph of provided vehicle
void
basic_cell::update_traction( TDynamicObject *Vehicle, int const Pantographindex ) {
// Winger 170204 - szukanie trakcji nad pantografami
auto const vFront = glm::make_vec3( Vehicle->VectorFront().getArray() ); // wektor normalny dla płaszczyzny ruchu pantografu
auto const vUp = glm::make_vec3( Vehicle->VectorUp().getArray() ); // wektor pionu pudła (pochylony od pionu na przechyłce)
auto const vLeft = glm::make_vec3( Vehicle->VectorLeft().getArray() ); // wektor odległości w bok (odchylony od poziomu na przechyłce)
auto const position = glm::dvec3 { Vehicle->GetPosition() }; // współrzędne środka pojazdu
auto pantograph = Vehicle->pants[ Pantographindex ].fParamPants;
auto const pantographposition = position + ( vLeft * pantograph->vPos.z ) + ( vUp * pantograph->vPos.y ) + ( vFront * pantograph->vPos.x );
for( auto *traction : m_directories.traction ) {
// współczynniki równania parametrycznego
auto const paramfrontdot = glm::dot( traction->vParametric, vFront );
auto const fRaParam =
-( glm::dot( traction->pPoint1, vFront ) - glm::dot( pantographposition, vFront ) )
/ ( paramfrontdot != 0.0 ?
paramfrontdot :
0.001 ); // div0 trap
if( ( fRaParam < -0.001 )
|| ( fRaParam > 1.001 ) ) { continue; }
// jeśli tylko jest w przedziale, wyznaczyć odległość wzdłuż wektorów vUp i vLeft
// punkt styku płaszczyzny z drutem (dla generatora łuku el.)
auto const vStyk = traction->pPoint1 + fRaParam * traction->vParametric;
// wektor musi się mieścić w przedziale ruchu pantografu
auto const vGdzie = vStyk - pantographposition;
auto fVertical = glm::dot( vGdzie, vUp );
if( fVertical >= 0.0 ) {
// jeśli ponad pantografem (bo może łapać druty spod wiaduktu)
auto const fHorizontal = std::abs( glm::dot( vGdzie, vLeft ) ) - pantograph->fWidth;
if( ( Global.bEnableTraction )
&& ( fVertical < pantograph->PantWys - 0.15 ) ) {
// jeśli drut jest niżej niż 15cm pod ślizgiem przełączamy w tryb połamania, o ile jedzie;
// (bEnableTraction) aby dało się jeździć na koślawych sceneriach
// i do tego jeszcze wejdzie pod ślizg
if( fHorizontal <= 0.0 ) {
// 0.635 dla AKP-1 AKP-4E
pantograph->PantWys = -1.0; // ujemna liczba oznacza połamanie
pantograph->hvPowerWire = nullptr; // bo inaczej się zasila w nieskończoność z połamanego
if( Vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo > 0 ) {
// liczba pantografów teraz będzie mniejsza
--Vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo;
}
if( DebugModeFlag ) {
ErrorLog( "Bad traction: " + Vehicle->name() + " broke pantograph at " + to_string( pantographposition ) );
}
}
}
else if( fVertical < pantograph->PantTraction ) {
// ale niżej, niż poprzednio znaleziony
if( fHorizontal <= 0.0 ) {
// 0.635 dla AKP-1 AKP-4E
// to się musi mieścić w przedziale zaleznym od szerokości pantografu
pantograph->hvPowerWire = traction; // jakiś znaleziony
pantograph->PantTraction = fVertical; // zapamiętanie nowej wysokości
}
else if( fHorizontal < pantograph->fWidthExtra ) {
// czy zmieścił się w zakresie nabieżnika? problem jest, gdy nowy drut jest wyżej,
// wtedy pantograf odłącza się od starego, a na podniesienie do nowego potrzebuje czasu
// korekta wysokości o nabieżnik - drut nad nabieżnikiem jest geometrycznie jakby nieco wyżej
fVertical += 0.15 * fHorizontal / pantograph->fWidthExtra;
if( fVertical < pantograph->PantTraction ) {
// gdy po korekcie jest niżej, niż poprzednio znaleziony
// gdyby to wystarczyło, to możemy go uznać
pantograph->hvPowerWire = traction; // może być
pantograph->PantTraction = fVertical; // na razie liniowo na nabieżniku, dokładność poprawi się później
}
}
}
}
}
}
// legacy method, updates sounds and polls event launchers within radius around specified point
void
basic_cell::update_events() {
// event launchers
for( auto *launcher : m_eventlaunchers ) {
if( ( true == launcher->check_conditions() )
&& ( SquareMagnitude( launcher->location() - Global.pCameraPosition ) < launcher->dRadius ) ) {
WriteLog( "Eventlauncher " + launcher->name() );
if( ( true == Global.shiftState )
&& ( launcher->Event2 != nullptr ) ) {
simulation::Events.AddToQuery( launcher->Event2, nullptr );
}
else if( launcher->Event1 ) {
simulation::Events.AddToQuery( launcher->Event1, nullptr );
}
}
}
}
// legacy method, updates sounds and polls event launchers within radius around specified point
void
basic_cell::update_sounds() {
auto const deltatime = Timer::GetDeltaRenderTime();
for( auto *sound : m_sounds ) {
sound->play_event();
}
// TBD, TODO: move to sound renderer
for( auto *path : m_paths ) {
// dźwięki pojazdów, również niewidocznych
path->RenderDynSounds();
}
}
// legacy method, triggers radio-stop procedure for all vehicles located on paths in the cell
void
basic_cell::radio_stop() {
for( auto *path : m_paths ) {
path->RadioStop();
}
}
// legacy method, adds specified path to the list of pieces undergoing state change
bool
basic_cell::RaTrackAnimAdd( TTrack *Track ) {
if( false == m_geometrycreated ) {
// nie ma animacji, gdy nie widać
return true;
}
if (tTrackAnim)
tTrackAnim->RaAnimListAdd(Track);
else
tTrackAnim = Track;
return false; // będzie animowane...
}
// legacy method, updates geometry for pieces in the animation list
void
basic_cell::RaAnimate( unsigned int const Framestamp ) {
if( ( tTrackAnim == nullptr )
|| ( Framestamp == m_framestamp ) ) {
// nie ma nic do animowania
return;
}
tTrackAnim = tTrackAnim->RaAnimate(); // przeliczenie animacji kolejnego
m_framestamp = Framestamp;
}
// sends content of the class to provided stream
void
basic_cell::serialize( std::ostream &Output ) const {
// region file version 0, cell data
// bounding area
m_area.serialize( Output );
// NOTE: cell activation flag is set dynamically on load
// cell shapes
// shape count followed by opaque shape data
sn_utils::ls_uint32( Output, m_shapesopaque.size() );
for( auto const &shape : m_shapesopaque ) {
shape.serialize( Output );
}
// shape count followed by translucent shape data
sn_utils::ls_uint32( Output, m_shapestranslucent.size() );
for( auto const &shape : m_shapestranslucent ) {
shape.serialize( Output );
}
// cell lines
// line count followed by lines data
sn_utils::ls_uint32( Output, m_lines.size() );
for( auto const &lines : m_lines ) {
lines.serialize( Output );
}
}
// restores content of the class from provided stream
void
basic_cell::deserialize( std::istream &Input ) {
// region file version 0, cell data
// bounding area
m_area.deserialize( Input );
// cell shapes
// shape count followed by opaque shape data
auto itemcount { sn_utils::ld_uint32( Input ) };
while( itemcount-- ) {
m_shapesopaque.emplace_back( shape_node().deserialize( Input ) );
}
itemcount = sn_utils::ld_uint32( Input );
while( itemcount-- ) {
m_shapestranslucent.emplace_back( shape_node().deserialize( Input ) );
}
itemcount = sn_utils::ld_uint32( Input );
while( itemcount-- ) {
m_lines.emplace_back( lines_node().deserialize( Input ) );
}
// cell activation flag
m_active = (
( false == m_shapesopaque.empty() )
|| ( false == m_shapestranslucent.empty() )
|| ( false == m_lines.empty() ) );
}
// adds provided shape to the cell
void
basic_cell::insert( shape_node Shape ) {
m_active = true;
// re-calculate cell radius, in case shape geometry extends outside the cell's boundaries
m_area.radius = std::max<float>(
m_area.radius,
glm::length( m_area.center - Shape.data().area.center ) + Shape.data().area.radius );
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 lines to the cell
void
basic_cell::insert( lines_node Lines ) {
m_active = true;
auto const &linesdata { Lines.data() };
for( auto &targetlines : m_lines ) {
// try to merge shapes with matching view ranges...
auto const &targetlinesdata { targetlines.data() };
if( ( linesdata.rangesquared_min == targetlinesdata.rangesquared_min )
&& ( linesdata.rangesquared_max == targetlinesdata.rangesquared_max )
// ...and located close to each other (within arbitrary limit of 10m)
&& ( glm::length( linesdata.area.center - targetlinesdata.area.center ) < 10.0 ) ) {
if( true == targetlines.merge( Lines ) ) {
// if the shape was merged there's nothing left to do
return;
}
}
}
// otherwise add the shape to the relevant list
Lines.origin( m_area.center );
m_lines.emplace_back( Lines );
}
// adds provided path to the cell
void
basic_cell::insert( TTrack *Path ) {
m_active = true;
Path->origin( m_area.center );
m_paths.emplace_back( Path );
// animation hook
Path->RaOwnerSet( this );
// re-calculate cell radius, in case track extends outside the cell's boundaries
m_area.radius = std::max(
m_area.radius,
static_cast<float>( glm::length( m_area.center - Path->location() ) + Path->radius() + 25.f ) ); // extra margin to prevent driven vehicle from flicking
}
// 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 );
// re-calculate cell bounding area, in case traction piece extends outside the cell's boundaries
enclose_area( 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 );
}
// re-calculate cell bounding area, in case model extends outside the cell's boundaries
enclose_area( Instance );
}
// adds provided sound instance to the cell
void
basic_cell::insert( sound_source *Sound ) {
m_active = true;
m_sounds.emplace_back( Sound );
}
// adds provided sound instance to the cell
void
basic_cell::insert( TEventLauncher *Launcher ) {
m_active = true;
m_eventlaunchers.emplace_back( Launcher );
// re-calculate cell bounding area, in case launcher range extends outside the cell's boundaries
enclose_area( Launcher );
}
// registers provided path in the lookup directory of the cell
void
basic_cell::register_end( TTrack *Path ) {
m_directories.paths.emplace_back( Path );
// eliminate potential duplicates
m_directories.paths.erase(
std::unique(
std::begin( m_directories.paths ),
std::end( m_directories.paths ) ),
std::end( m_directories.paths ) );
}
// registers provided traction piece in the lookup directory of the cell
void
basic_cell::register_end( TTraction *Traction ) {
m_directories.traction.emplace_back( Traction );
// eliminate potential duplicates
m_directories.traction.erase(
std::unique(
std::begin( m_directories.traction ),
std::end( m_directories.traction ) ),
std::end( m_directories.traction ) );
}
// find a vehicle located nearest to specified point, within specified radius, optionally ignoring vehicles without drivers. reurns: located vehicle and distance
std::tuple<TDynamicObject *, float>
basic_cell::find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ) const {
TDynamicObject *vehiclenearest { nullptr };
float leastdistance { std::numeric_limits<float>::max() };
float distance;
float const distancecutoff { Radius * Radius }; // we'll ignore vehicles farther than this
for( auto *path : m_paths ) {
for( auto *vehicle : path->Dynamics ) {
if( ( true == Onlycontrolled )
&& ( vehicle->Mechanik == nullptr ) ) {
continue;
}
if( false == Findbycoupler ) {
// basic search, checks vehicles' center points
distance = glm::length2( glm::dvec3{ vehicle->GetPosition() } - Point );
}
else {
// alternative search, checks positions of vehicles' couplers
distance = std::min(
glm::length2( glm::dvec3{ vehicle->HeadPosition() } - Point ),
glm::length2( glm::dvec3{ vehicle->RearPosition() } - Point ) );
}
if( ( distance > distancecutoff )
|| ( distance > leastdistance ) ){
continue;
}
std::tie( vehiclenearest, leastdistance ) = std::tie( vehicle, distance );
}
}
return std::tie( vehiclenearest, leastdistance );
}
// finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint
std::tuple<TTrack *, int>
basic_cell::find( glm::dvec3 const &Point, TTrack const *Exclude ) const {
Math3D::vector3 point { Point.x, Point.y, Point.z }; // sad workaround until math classes unification
int endpointid;
for( auto *path : m_directories.paths ) {
if( path == Exclude ) { continue; }
endpointid = path->TestPoint( &point );
if( endpointid >= 0 ) {
return std::tie( path, endpointid );
}
}
return { nullptr, -1 };
}
// finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint
std::tuple<TTraction *, int>
basic_cell::find( glm::dvec3 const &Point, TTraction const *Exclude ) const {
int endpointid;
for( auto *traction : m_directories.traction ) {
if( traction == Exclude ) { continue; }
endpointid = traction->TestPoint( Point );
if( endpointid >= 0 ) {
return std::tie( traction, endpointid );
}
}
return { nullptr, -1 };
}
// finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece
std::tuple<TTraction *, int, float>
basic_cell::find( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ) const {
TTraction
*tractionnearest { nullptr };
float
distance,
distancenearest { std::numeric_limits<float>::max() };
int endpoint,
endpointnearest { -1 };
for( auto *traction : m_directories.traction ) {
if( ( traction == Other )
|| ( traction->psSection != Other->psSection )
|| ( traction == Other->hvNext[ 0 ] )
|| ( traction == Other->hvNext[ 1 ] ) ) {
// ignore pieces from different sections, and ones connected to the other piece
continue;
}
endpoint = (
glm::dot( traction->vParametric, Other->vParametric ) >= 0.0 ?
Currentdirection ^ 1 :
Currentdirection );
if( ( traction->psPower[ endpoint ] == nullptr )
|| ( traction->fResistance[ endpoint ] < 0.0 ) ) {
continue;
}
distance = glm::length2( traction->location() - Point );
if( distance < distancenearest ) {
std::tie( tractionnearest, endpointnearest, distancenearest ) = std::tie( traction, endpoint, distance );
}
}
return { tractionnearest, endpointnearest, distancenearest };
}
// 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
}
// generates renderable version of held non-instanced geometry
void
basic_cell::create_geometry( gfx::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 ); }
for( auto *path : m_paths ) { path->create_geometry( Bank ); }
for( auto *traction : m_traction ) { traction->create_geometry( Bank ); }
for( auto &lines : m_lines ) { lines.create_geometry( Bank ); }
// arrange content by assigned materials to minimize state switching
std::sort(
std::begin( m_paths ), std::end( m_paths ),
TTrack::sort_by_material );
m_geometrycreated = true; // helper for legacy animation code, get rid of it after refactoring
}
// adjusts cell bounding area to enclose specified node
void
basic_cell::enclose_area( editor::basic_node *Node ) {
m_area.radius = std::max(
m_area.radius,
static_cast<float>( glm::length( m_area.center - Node->location() ) + Node->radius() ) );
}
// legacy method, finds and assigns traction piece(s) to pantographs of provided vehicle
void
basic_section::update_traction( TDynamicObject *Vehicle, int const Pantographindex ) {
auto const vFront = glm::make_vec3( Vehicle->VectorFront().getArray() ); // wektor normalny dla płaszczyzny ruchu pantografu
auto const vUp = glm::make_vec3( Vehicle->VectorUp().getArray() ); // wektor pionu pudła (pochylony od pionu na przechyłce)
auto const vLeft = glm::make_vec3( Vehicle->VectorLeft().getArray() ); // wektor odległości w bok (odchylony od poziomu na przechyłce)
auto const position = glm::dvec3{ Vehicle->GetPosition() }; // współrzędne środka pojazdu
auto pantograph = Vehicle->pants[ Pantographindex ].fParamPants;
auto const pantographposition = position + ( vLeft * pantograph->vPos.z ) + ( vUp * pantograph->vPos.y ) + ( vFront * pantograph->vPos.x );
auto const radius { EU07_CELLSIZE * 0.5 }; // redius around point of interest
for( auto &cell : m_cells ) {
// we reject early cells which aren't within our area of interest
if( glm::length2( cell.area().center - pantographposition ) < ( ( cell.area().radius + radius ) * ( cell.area().radius + radius ) ) ) {
cell.update_traction( Vehicle, Pantographindex );
}
}
}
// legacy method, polls event launchers within radius around specified point
void
basic_section::update_events( glm::dvec3 const &Location, float const Radius ) {
for( auto &cell : m_cells ) {
if( glm::length2( cell.area().center - Location ) < ( ( cell.area().radius + Radius ) * ( cell.area().radius + Radius ) ) ) {
// we reject cells which aren't within our area of interest
cell.update_events();
}
}
}
// legacy method, updates sounds within radius around specified point
void
basic_section::update_sounds( glm::dvec3 const &Location, float const Radius ) {
for( auto &cell : m_cells ) {
if( glm::length2( cell.area().center - Location ) < ( ( cell.area().radius + Radius ) * ( cell.area().radius + Radius ) ) ) {
// we reject cells which aren't within our area of interest
cell.update_sounds();
}
}
}
// legacy method, triggers radio-stop procedure for all vehicles in 2km radius around specified location
void
basic_section::radio_stop( glm::dvec3 const &Location, float const Radius ) {
for( auto &cell : m_cells ) {
if( glm::length2( cell.area().center - Location ) < ( ( cell.area().radius + Radius ) * ( cell.area().radius + Radius ) ) ) {
// we reject cells which aren't within our area of interest
cell.radio_stop();
}
}
}
// sends content of the class to provided stream
void
basic_section::serialize( std::ostream &Output ) const {
auto const sectionstartpos { Output.tellp() };
// region file version 0, section data
// section size
sn_utils::ls_uint32( Output, 0 );
// bounding area
m_area.serialize( Output );
// section shapes: shape count followed by shape data
sn_utils::ls_uint32( Output, m_shapes.size() );
for( auto const &shape : m_shapes ) {
shape.serialize( Output );
}
// partitioned data
for( auto const &cell : m_cells ) {
cell.serialize( Output );
}
// all done; calculate and record section size
auto const sectionendpos { Output.tellp() };
Output.seekp( sectionstartpos );
sn_utils::ls_uint32( Output, static_cast<uint32_t>( ( sizeof( uint32_t ) + ( sectionendpos - sectionstartpos ) ) ) );
Output.seekp( sectionendpos );
}
// restores content of the class from provided stream
void
basic_section::deserialize( std::istream &Input ) {
// region file version 0, section data
// bounding area
m_area.deserialize( Input );
// section shapes: shape count followed by shape data
auto shapecount { sn_utils::ld_uint32( Input ) };
while( shapecount-- ) {
m_shapes.emplace_back( shape_node().deserialize( Input ) );
}
// partitioned data
for( auto &cell : m_cells ) {
cell.deserialize( Input );
}
}
// adds provided shape to the section
void
basic_section::insert( shape_node Shape ) {
auto const &shapedata = Shape.data();
// re-calculate section radius, in case shape geometry extends outside the section's boundaries
m_area.radius = std::max<float>(
m_area.radius,
static_cast<float>( glm::length( m_area.center - shapedata.area.center ) + shapedata.area.radius ) );
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 lines to the section
void
basic_section::insert( lines_node Lines ) {
cell( Lines.data().area.center ).insert( Lines );
}
// find a vehicle located nearest to specified point, within specified radius, optionally ignoring vehicles without drivers. reurns: located vehicle and distance
std::tuple<TDynamicObject *, float>
basic_section::find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ) {
// go through sections within radius of interest, and pick the nearest candidate
TDynamicObject
*vehiclefound,
*vehiclenearest { nullptr };
float
distancefound,
distancenearest { std::numeric_limits<float>::max() };
for( auto &cell : m_cells ) {
// we reject early cells which aren't within our area of interest
if( glm::length2( cell.area().center - Point ) > ( ( cell.area().radius + Radius ) * ( cell.area().radius + Radius ) ) ) {
continue;
}
std::tie( vehiclefound, distancefound ) = cell.find( Point, Radius, Onlycontrolled, Findbycoupler );
if( ( vehiclefound != nullptr )
&& ( distancefound < distancenearest ) ) {
std::tie( vehiclenearest, distancenearest ) = std::tie( vehiclefound, distancefound );
}
}
return std::tie( vehiclenearest, distancenearest );
}
// finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint
std::tuple<TTrack *, int>
basic_section::find( glm::dvec3 const &Point, TTrack const *Exclude ) {
return cell( Point ).find( Point, Exclude );
}
// finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint
std::tuple<TTraction *, int>
basic_section::find( glm::dvec3 const &Point, TTraction const *Exclude ) {
return cell( Point ).find( Point, Exclude );
}
// finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece
std::tuple<TTraction *, int, float>
basic_section::find( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ) {
// go through sections within radius of interest, and pick the nearest candidate
TTraction
*tractionfound,
*tractionnearest { nullptr };
float
distancefound,
distancenearest { std::numeric_limits<float>::max() };
int
endpointfound,
endpointnearest { -1 };
auto const radius { 0.0 }; // { EU07_CELLSIZE * 0.5 }; // experimentally limited, check if it has any negative effect
for( auto &cell : m_cells ) {
// we reject early cells which aren't within our area of interest
if( glm::length2( cell.area().center - Point ) > ( ( cell.area().radius + radius ) * ( cell.area().radius + radius ) ) ) {
continue;
}
std::tie( tractionfound, endpointfound, distancefound ) = cell.find( Point, Other, Currentdirection );
if( ( tractionfound != nullptr )
&& ( distancefound < distancenearest ) ) {
std::tie( tractionnearest, endpointnearest, distancenearest ) = std::tie( tractionfound, endpointfound, distancefound );
}
}
return { tractionnearest, endpointnearest, distancenearest };
}
// 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<int>( std::floor( ( Location.x - ( m_area.center.x - EU07_SECTIONSIZE / 2 ) ) / EU07_CELLSIZE ) );
auto const row = static_cast<int>( 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 );
}
basic_region::~basic_region() {
for( auto *section : m_sections ) { if( section != nullptr ) { delete section; } }
}
// legacy method, polls event launchers around camera
void
basic_region::update_events() {
// render events and sounds from sectors near enough to the viewer
auto const range = EU07_SECTIONSIZE; // arbitrary range
auto const &sectionlist = sections( Global.pCameraPosition, range );
for( auto *section : sectionlist ) {
section->update_events( Global.pCameraPosition, range );
}
}
// legacy method, updates sounds and polls event launchers around camera
void
basic_region::update_sounds() {
// render events and sounds from sectors near enough to the viewer
auto const range = 2750.f; // audible range of 100 db sound
auto const &sectionlist = sections( Global.pCameraPosition, range );
for( auto *section : sectionlist ) {
section->update_sounds( Global.pCameraPosition, range );
}
}
// legacy method, finds and assigns traction piece(s) to pantographs of provided vehicle
void
basic_region::update_traction( TDynamicObject *Vehicle, int const Pantographindex ) {
// TODO: convert vectors to transformation matrix and pass them down the chain along with calculated position
auto const vFront = glm::make_vec3( Vehicle->VectorFront().getArray() ); // wektor normalny dla płaszczyzny ruchu pantografu
auto const vUp = glm::make_vec3( Vehicle->VectorUp().getArray() ); // wektor pionu pudła (pochylony od pionu na przechyłce)
auto const vLeft = glm::make_vec3( Vehicle->VectorLeft().getArray() ); // wektor odległości w bok (odchylony od poziomu na przechyłce)
auto const position = glm::dvec3 { Vehicle->GetPosition() }; // współrzędne środka pojazdu
auto p = Vehicle->pants[ Pantographindex ].fParamPants;
auto const pant0 = position + ( vLeft * p->vPos.z ) + ( vUp * p->vPos.y ) + ( vFront * p->vPos.x );
p->PantTraction = std::numeric_limits<double>::max(); // taka za duża wartość
auto const &sectionlist = sections( pant0, EU07_CELLSIZE * 0.5 );
for( auto *section : sectionlist ) {
section->update_traction( Vehicle, Pantographindex );
}
}
// stores content of the class in file with specified name
void
basic_region::serialize( std::string const &Scenariofile ) const {
auto filename { Scenariofile };
while( filename[ 0 ] == '$' ) {
// trim leading $ char rainsted utility may add to the base name for modified .scn files
filename.erase( 0, 1 );
}
filename = Global.asCurrentSceneryPath + filename;
if( ( filename.rfind( '.' ) != std::string::npos )
&& ( filename.rfind( '.' ) != filename.rfind( ".." ) + 1 ) ) {
// trim extension, it's typically going to be for different file type
filename.erase( filename.rfind( '.' ) );
}
filename += EU07_FILEEXTENSION_REGION;
std::ofstream output { filename, std::ios::binary };
// region file version 1
// header: EU07SBT + version (0-255)
sn_utils::ls_uint32( output, MAKE_ID4( 'E', 'U', '0', '7' ) );
sn_utils::ls_uint32( output, MAKE_ID4( 'S', 'B', 'T', 1 ) );
// sections
// TBD, TODO: build table of sections and file offsets, if we postpone section loading until they're within range
std::uint32_t sectioncount { 0 };
for( auto *section : m_sections ) {
if( section != nullptr ) {
++sectioncount;
}
}
// section count, followed by section data
sn_utils::ls_uint32( output, sectioncount );
std::uint32_t sectionindex { 0 };
for( auto *section : m_sections ) {
// section data: section index, followed by length of section data, followed by section data
if( section != nullptr ) {
sn_utils::ls_uint32( output, sectionindex );
section->serialize( output ); }
++sectionindex;
}
}
// restores content of the class from file with specified name. returns: true on success, false otherwise
bool
basic_region::deserialize( std::string const &Scenariofile ) {
auto filename { Scenariofile };
while( filename[ 0 ] == '$' ) {
// trim leading $ char rainsted utility may add to the base name for modified .scn files
filename.erase( 0, 1 );
}
filename = Global.asCurrentSceneryPath + filename;
if( ( filename.rfind( '.' ) != std::string::npos )
&& ( filename.rfind( '.' ) != filename.rfind( ".." ) + 1 ) ) {
// trim extension, it's typically going to be for different file type
filename.erase( filename.rfind( '.' ) );
}
filename += EU07_FILEEXTENSION_REGION;
if( false == FileExists( filename ) ) {
return false;
}
// region file version 1
// file type and version check
std::ifstream input( filename, std::ios::binary );
uint32_t headermain { sn_utils::ld_uint32( input ) };
uint32_t headertype { sn_utils::ld_uint32( input ) };
if( ( headermain != MAKE_ID4( 'E', 'U', '0', '7' )
|| ( headertype != MAKE_ID4( 'S', 'B', 'T', 1 ) ) ) ) {
// wrong file type
WriteLog( "Bad file: \"" + filename + "\" is of either unrecognized type or version" );
return false;
}
// sections
// TBD, TODO: build table of sections and file offsets, if we postpone section loading until they're within range
// section count
auto sectioncount { sn_utils::ld_uint32( input ) };
while( sectioncount-- ) {
// section index, followed by section data size, followed by section data
auto *&section { m_sections[ sn_utils::ld_uint32( input ) ] };
auto const sectionsize { sn_utils::ld_uint32( input ) };
section = new basic_section();
section->deserialize( input );
}
return true;
}
// legacy method, links specified path piece with potential neighbours
void
basic_region::TrackJoin( TTrack *Track ) {
// wyszukiwanie sąsiednich torów do podłączenia (wydzielone na użytek obrotnicy)
TTrack *matchingtrack;
int endpointid;
if( Track->CurrentPrev() == nullptr ) {
std::tie( matchingtrack, endpointid ) = find_path( Track->CurrentSegment()->FastGetPoint_0(), Track );
switch( endpointid ) {
case 0:
Track->ConnectPrevPrev( matchingtrack, 0 );
break;
case 1:
Track->ConnectPrevNext( matchingtrack, 1 );
break;
}
}
if( Track->CurrentNext() == nullptr ) {
std::tie( matchingtrack, endpointid ) = find_path( Track->CurrentSegment()->FastGetPoint_1(), Track );
switch( endpointid ) {
case 0:
Track->ConnectNextPrev( matchingtrack, 0 );
break;
case 1:
Track->ConnectNextNext( matchingtrack, 1 );
break;
}
}
}
// legacy method, triggers radio-stop procedure for all vehicles in 2km radius around specified location
void
basic_region::RadioStop( glm::dvec3 const &Location ) {
auto const range = 2000.f;
auto const &sectionlist = sections( Location, range );
for( auto *section : sectionlist ) {
section->radio_stop( Location, range );
}
}
void
basic_region::insert_shape( shape_node Shape, scratch_data &Scratchpad, bool const Transform ) {
// 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<shape_node> shapes { Shape };
auto &shape = shapes.front();
if( shape.m_data.vertices.empty() ) { return; }
// adjust input if necessary:
if( true == Transform ) {
// shapes generated from legacy terrain come with world space coordinates and don't need processing
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<double>( vertex.position, rotation.z );
vertex.position = glm::rotateX<double>( vertex.position, rotation.x );
vertex.position = glm::rotateY<double>( 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
}
}
}
// move the data into appropriate section(s)
for( auto &shape : shapes ) {
// with the potential splitting done we can calculate each chunk's bounding radius
shape.compute_radius();
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 lines in the region
void
basic_region::insert_lines( lines_node Lines, scratch_data &Scratchpad ) {
if( Lines.m_data.vertices.empty() ) { return; }
// transform point coordinates if needed
if( Scratchpad.location.rotation != glm::vec3( 0, 0, 0 ) ) {
// rotate...
auto const rotation = glm::radians( Scratchpad.location.rotation );
for( auto &vertex : Lines.m_data.vertices ) {
vertex.position = glm::rotateZ<double>( vertex.position, rotation.z );
vertex.position = glm::rotateX<double>( vertex.position, rotation.x );
vertex.position = glm::rotateY<double>( vertex.position, 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 : Lines.m_data.vertices ) {
vertex.position += offset;
}
}
// calculate bounding area
for( auto const &vertex : Lines.m_data.vertices ) {
Lines.m_data.area.center += vertex.position;
}
Lines.m_data.area.center /= Lines.m_data.vertices.size();
Lines.compute_radius();
// move the data into appropriate section
if( point_inside( Lines.m_data.area.center ) ) {
// NOTE: nodes placed outside of region boundaries are discarded
section( Lines.m_data.area.center ).insert( Lines );
}
else {
ErrorLog(
"Bad scenario: lines node" + (
Lines.m_name.empty() ?
"" :
" \"" + Lines.m_name + "\"" )
+ " placed in location outside region bounds (" + to_string( Lines.m_data.area.center ) + ")" );
}
}
// inserts provided track in the region
void
basic_region::insert_path( TTrack *Path, const scratch_data &Scratchpad ) {
// NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done
auto location = Path->location();
if( point_inside( location ) ) {
// NOTE: nodes placed outside of region boundaries are discarded
section( location ).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( location ) + ")" );
}
// also register path ends in appropriate sections, for path merging lookups
// TODO: clean this up during track refactoring
for( auto &point : Path->endpoints() ) {
register_path( Path, point );
}
}
// 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 location = Traction->location();
if( point_inside( location ) ) {
// NOTE: nodes placed outside of region boundaries are discarded
section( location ).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( location ) + ")" );
}
// also register traction ends in appropriate sections, for path merging lookups
// TODO: clean this up during track refactoring
for( auto &point : Traction->endpoints() ) {
register_traction( Traction, point );
}
}
// 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 location = Instance->location();
if( point_inside( location ) ) {
// NOTE: nodes placed outside of region boundaries are discarded
section( location ).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( location ) + ")" );
}
}
// inserts provided sound in the region
void
basic_region::insert_sound( sound_source *Sound, scratch_data &Scratchpad ) {
// NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done
auto location = Sound->location();
if( point_inside( location ) ) {
// NOTE: nodes placed outside of region boundaries are discarded
section( location ).insert( Sound );
}
else {
// tracks are guaranteed to hava a name so we can skip the check
ErrorLog( "Bad scenario: sound node \"" + Sound->name() + "\" placed in location outside region bounds (" + to_string( location ) + ")" );
}
}
// inserts provided event launcher in the region
void
basic_region::insert_launcher( TEventLauncher *Launcher, scratch_data &Scratchpad ) {
// NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done
auto location = Launcher->location();
if( point_inside( location ) ) {
// NOTE: nodes placed outside of region boundaries are discarded
section( location ).insert( Launcher );
}
else {
// tracks are guaranteed to hava a name so we can skip the check
ErrorLog( "Bad scenario: event launcher \"" + Launcher->name() + "\" placed in location outside region bounds (" + to_string( location ) + ")" );
}
}
// find a vehicle located neares to specified location, within specified radius, optionally discarding vehicles without drivers
std::tuple<TDynamicObject *, float>
basic_region::find_vehicle( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ) {
auto const &sectionlist = sections( Point, Radius );
// go through sections within radius of interest, and pick the nearest candidate
TDynamicObject
*foundvehicle,
*nearestvehicle { nullptr };
float
founddistance,
nearestdistance { std::numeric_limits<float>::max() };
for( auto *section : sectionlist ) {
std::tie( foundvehicle, founddistance ) = section->find( Point, Radius, Onlycontrolled, Findbycoupler );
if( ( foundvehicle != nullptr )
&& ( founddistance < nearestdistance ) ) {
std::tie( nearestvehicle, nearestdistance ) = std::tie( foundvehicle, founddistance );
}
}
return std::tie( nearestvehicle, nearestdistance );
}
// finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint
std::tuple<TTrack *, int>
basic_region::find_path( glm::dvec3 const &Point, TTrack const *Exclude ) {
// TBD: throw out of bounds exception instead of checks all over the place..?
if( point_inside( Point ) ) {
return section( Point ).find( Point, Exclude );
}
return std::make_tuple<TTrack *, int>( nullptr, -1 );
}
// finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint
std::tuple<TTraction *, int>
basic_region::find_traction( glm::dvec3 const &Point, TTraction const *Exclude ) {
// TBD: throw out of bounds exception instead of checks all over the place..?
if( point_inside( Point ) ) {
return section( Point ).find( Point, Exclude );
}
return std::make_tuple<TTraction *, int>( nullptr, -1 );
}
// finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece
std::tuple<TTraction *, int>
basic_region::find_traction( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ) {
auto const &sectionlist = sections( Point, 0.f );
// go through sections within radius of interest, and pick the nearest candidate
TTraction
*tractionfound,
*tractionnearest { nullptr };
float
distancefound,
distancenearest { std::numeric_limits<float>::max() };
int
endpointfound,
endpointnearest { -1 };
for( auto *section : sectionlist ) {
std::tie( tractionfound, endpointfound, distancefound ) = section->find( Point, Other, Currentdirection );
if( ( tractionfound != nullptr )
&& ( distancefound < distancenearest ) ) {
std::tie( tractionnearest, endpointnearest, distancenearest ) = std::tie( tractionfound, endpointfound, distancefound );
}
}
return { tractionnearest, endpointnearest };
}
// finds sections inside specified sphere. returns: list of sections
std::vector<basic_section *> const &
basic_region::sections( glm::dvec3 const &Point, float const Radius ) {
m_scratchpad.sections.clear();
auto const centerx { static_cast<int>( std::floor( Point.x / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ) };
auto const centerz { static_cast<int>( std::floor( Point.z / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ) };
auto const sectioncount { 2 * static_cast<int>( std::ceil( Radius / EU07_SECTIONSIZE ) ) };
int const originx = centerx - sectioncount / 2;
int const originz = centerz - sectioncount / 2;
auto const padding { 0.0 }; // { EU07_SECTIONSIZE * 0.25 }; // TODO: check if we can get away with padding of 0
for( int row = originz; row <= originz + sectioncount; ++row ) {
if( row < 0 ) { continue; }
if( row >= EU07_REGIONSIDESECTIONCOUNT ) { break; }
for( int column = originx; column <= originx + sectioncount; ++column ) {
if( column < 0 ) { continue; }
if( column >= EU07_REGIONSIDESECTIONCOUNT ) { break; }
auto *section { m_sections[ row * EU07_REGIONSIDESECTIONCOUNT + column ] };
if( ( section != nullptr )
&& ( glm::length2( section->area().center - Point ) <= ( ( section->area().radius + padding + Radius ) * ( section->area().radius + padding + Radius ) ) ) ) {
m_scratchpad.sections.emplace_back( section );
}
}
}
return m_scratchpad.sections;
}
// registers specified path in the lookup directory of a cell enclosing specified point
void
basic_region::register_path( TTrack *Path, glm::dvec3 const &Point ) {
if( point_inside( Point ) ) {
section( Point ).register_node( Path, Point );
}
}
// registers specified end point of the provided traction piece in the lookup directory of the region
void
basic_region::register_traction( TTraction *Traction, glm::dvec3 const &Point ) {
if( point_inside( Point ) ) {
section( Point ).register_node( Traction, Point );
}
}
// 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<shape_node> &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<int>( std::floor( Location.x / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ) };
auto const row { static_cast<int>( std::floor( Location.z / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ) };
auto &section =
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
//---------------------------------------------------------------------------