mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-03-22 15:05:03 +01:00
1602 lines
61 KiB
C++
1602 lines
61 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 "camera.h"
|
|
#include "animmodel.h"
|
|
#include "event.h"
|
|
#include "evlaunch.h"
|
|
#include "timer.h"
|
|
#include "logs.h"
|
|
#include "sn_utils.h"
|
|
#include "renderer.h"
|
|
|
|
namespace scene {
|
|
|
|
std::string const EU07_FILEEXTENSION_REGION { ".sbt" };
|
|
std::uint32_t const EU07_FILEHEADER { MAKE_ID4( 'E','U','0','7' ) };
|
|
std::uint32_t const EU07_FILEVERSION_REGION { MAKE_ID4( 'S', 'B', 'T', 1 ) };
|
|
|
|
// potentially activates event handler with the same name as provided node, and within handler activation range
|
|
void
|
|
basic_cell::on_click( TAnimModel const *Instance ) {
|
|
|
|
for( auto *launcher : m_eventlaunchers ) {
|
|
if( ( launcher->name() == Instance->name() )
|
|
&& ( glm::length2( launcher->location() - Instance->location() ) < launcher->dRadius )
|
|
&& ( true == launcher->check_conditions() ) ) {
|
|
launch_event( launcher );
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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_activation() && launcher->check_conditions() ) )
|
|
&& ( SquareMagnitude( launcher->location() - Global.pCamera.Pos ) < launcher->dRadius ) ) {
|
|
|
|
launch_event( launcher );
|
|
}
|
|
}
|
|
}
|
|
|
|
// legacy method, updates sounds and polls event launchers within radius around specified point
|
|
void
|
|
basic_cell::update_sounds() {
|
|
|
|
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 = (
|
|
( true == m_active )
|
|
|| ( false == m_shapesopaque.empty() )
|
|
|| ( false == m_shapestranslucent.empty() )
|
|
|| ( false == m_lines.empty() ) );
|
|
}
|
|
|
|
// sends content of the class in legacy (text) format to provided stream
|
|
void
|
|
basic_cell::export_as_text( std::ostream &Output ) const {
|
|
|
|
// text format export dumps only relevant basic objects
|
|
// sounds
|
|
for( auto const *sound : m_sounds ) {
|
|
sound->export_as_text( Output );
|
|
}
|
|
}
|
|
|
|
// 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 );
|
|
// NOTE: sound sources are virtual 'points' hence they don't ever expand cell range
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
|
|
// adds provided memory cell to the cell
|
|
void
|
|
basic_cell::insert( TMemCell *Memorycell ) {
|
|
|
|
m_active = true;
|
|
|
|
m_memorycells.emplace_back( Memorycell );
|
|
// NOTE: memory cells are virtual 'points' hence they don't ever expand cell range
|
|
}
|
|
|
|
// removes provided model instance from the cell
|
|
void
|
|
basic_cell::erase( TAnimModel *Instance ) {
|
|
|
|
auto const flags = Instance->Flags();
|
|
auto alpha =
|
|
( Instance->Material() != nullptr ?
|
|
Instance->Material()->textures_alpha :
|
|
0x30300030 );
|
|
|
|
if( alpha & flags & 0x2F2F002F ) {
|
|
// instance has translucent pieces
|
|
m_instancetranslucent.erase(
|
|
std::remove_if(
|
|
std::begin( m_instancetranslucent ), std::end( m_instancetranslucent ),
|
|
[=]( TAnimModel *instance ) {
|
|
return instance == Instance; } ),
|
|
std::end( m_instancetranslucent ) );
|
|
}
|
|
alpha ^= 0x0F0F000F; // odwrócenie flag tekstur, aby wyłapać nieprzezroczyste
|
|
if( alpha & flags & 0x1F1F001F ) {
|
|
// instance has opaque pieces
|
|
m_instancesopaque.erase(
|
|
std::remove_if(
|
|
std::begin( m_instancesopaque ), std::end( m_instancesopaque ),
|
|
[=]( TAnimModel *instance ) {
|
|
return instance == Instance; } ),
|
|
std::end( m_instancesopaque ) );
|
|
}
|
|
// TODO: update cell bounding area
|
|
}
|
|
|
|
// removes provided memory cell from the cell
|
|
void
|
|
basic_cell::erase( TMemCell *Memorycell ) {
|
|
|
|
m_memorycells.erase(
|
|
std::remove_if(
|
|
std::begin( m_memorycells ), std::end( m_memorycells ),
|
|
[=]( TMemCell *memorycell ) {
|
|
return memorycell == Memorycell; } ),
|
|
std::end( m_memorycells ) );
|
|
}
|
|
|
|
// 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 { 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 { 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 { 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
|
|
}
|
|
|
|
// executes event assigned to specified launcher
|
|
void
|
|
basic_cell::launch_event( TEventLauncher *Launcher ) {
|
|
|
|
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 );
|
|
}
|
|
}
|
|
|
|
// adjusts cell bounding area to enclose specified node
|
|
void
|
|
basic_cell::enclose_area( scene::basic_node *Node ) {
|
|
|
|
m_area.radius = std::max(
|
|
m_area.radius,
|
|
static_cast<float>( glm::length( m_area.center - Node->location() ) + Node->radius() ) );
|
|
}
|
|
|
|
|
|
|
|
// potentially activates event handler with the same name as provided node, and within handler activation range
|
|
void
|
|
basic_section::on_click( TAnimModel const *Instance ) {
|
|
|
|
cell( Instance->location() ).on_click( Instance );
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
}
|
|
|
|
// sends content of the class in legacy (text) format to provided stream
|
|
void
|
|
basic_section::export_as_text( std::ostream &Output ) const {
|
|
|
|
// text format export dumps only relevant basic objects from non-empty cells
|
|
for( auto const &cell : m_cells ) {
|
|
cell.export_as_text( Output );
|
|
}
|
|
}
|
|
|
|
// 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 { 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; } }
|
|
}
|
|
|
|
// potentially activates event handler with the same name as provided node, and within handler activation range
|
|
void
|
|
basic_region::on_click( TAnimModel const *Instance ) {
|
|
|
|
if( Instance->name().empty() || ( Instance->name() == "none" ) ) { return; }
|
|
|
|
auto const location { Instance->location() };
|
|
|
|
if( point_inside( location ) ) {
|
|
section( location ).on_click( Instance );
|
|
}
|
|
}
|
|
|
|
// 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 §ionlist = sections( Global.pCamera.Pos, range );
|
|
for( auto *section : sectionlist ) {
|
|
section->update_events( Global.pCamera.Pos, 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 §ionlist = sections( Global.pCamera.Pos, range );
|
|
for( auto *section : sectionlist ) {
|
|
section->update_sounds( Global.pCamera.Pos, 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 §ionlist = sections( pant0, EU07_CELLSIZE * 0.5 );
|
|
for( auto *section : sectionlist ) {
|
|
section->update_traction( Vehicle, Pantographindex );
|
|
}
|
|
}
|
|
|
|
// checks whether specified file is a valid region data file
|
|
bool
|
|
basic_region::is_scene( 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 );
|
|
}
|
|
erase_extension( filename );
|
|
filename = Global.asCurrentSceneryPath + filename;
|
|
filename += EU07_FILEEXTENSION_REGION;
|
|
|
|
if( false == FileExists( filename ) ) {
|
|
return false;
|
|
}
|
|
// 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 != EU07_FILEHEADER
|
|
|| ( headertype != EU07_FILEVERSION_REGION ) ) ) {
|
|
// wrong file type
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
erase_extension( filename );
|
|
filename = Global.asCurrentSceneryPath + filename;
|
|
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, EU07_FILEHEADER );
|
|
sn_utils::ls_uint32( output, EU07_FILEVERSION_REGION );
|
|
// 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 );
|
|
}
|
|
erase_extension( filename );
|
|
filename = Global.asCurrentSceneryPath + filename;
|
|
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 != EU07_FILEHEADER
|
|
|| ( headertype != EU07_FILEVERSION_REGION ) ) ) {
|
|
// 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 const sectionindex { sn_utils::ld_uint32( input ) };
|
|
auto const sectionsize { sn_utils::ld_uint32( input ) };
|
|
if( m_sections[ sectionindex ] == nullptr ) {
|
|
m_sections[ sectionindex ] = new basic_section();
|
|
}
|
|
m_sections[ sectionindex ]->deserialize( input );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// sends content of the class in legacy (text) format to provided stream
|
|
void
|
|
basic_region::export_as_text( std::ostream &Output ) const {
|
|
|
|
for( auto *section : m_sections ) {
|
|
// text format export dumps only relevant basic objects from non-empty sections
|
|
if( section != nullptr ) {
|
|
section->export_as_text( Output );
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 §ionlist = sections( Location, range );
|
|
for( auto *section : sectionlist ) {
|
|
section->radio_stop( Location, range );
|
|
}
|
|
}
|
|
|
|
void
|
|
basic_region::insert( 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_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 ) + ")" );
|
|
}
|
|
}
|
|
|
|
// 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 §ionlist = 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 { 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 { 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 { 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 §ionlist = 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;
|
|
}
|
|
|
|
// 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 §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
|
|
|
|
//---------------------------------------------------------------------------
|