mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 06:39:18 +02:00
The progressive load previously streamed the deferred visual nodes (3d model instances + terrain shapes/lines) in file order, so distant scenery could load before the player's surroundings. This builds them nearest-camera first instead. The visual pass now runs in two steps. Enumeration replays the twin and captures each visual node verbatim (its resolved tokens as text -- numbers round-trip losslessly through cParser) together with the transform/group context it was read under and, for models, its transformed world position. Once the replay is exhausted the records are sorted by squared distance to the camera (terrain shapes first so the ground appears before the props on it), then built a budgeted slice per frame through the normal node path with the captured transform and group restored -- so placement, grouping and the per-cell instance buckets come out identical to an in-order load. Two supporting fixes make out-of-order/late insertion correct: - a cell/section whose geometry was already baked (the renderer finalised it before a deferred node arrived) now appends the new shape/lines straight into its live geometry bank instead of merging into vertex-freed geometry, which would silently drop it; create_geometry() remembers the bank for every cell. - events that bind to visual model instances (lights/animation/texture/visible) are deferred from InitEvents() to a new InitInstanceEvents() run after the visual nodes are built, so their target models exist when they initialise. Verified on td.scn: playable ~2s, 540 deferred nodes enumerated and built nearest-first ~0.5s later, no duplicate instances. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1687 lines
64 KiB
C++
1687 lines
64 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/scene.h"
|
|
|
|
#include "simulation/simulation.h"
|
|
#include "utilities/Globals.h"
|
|
#include "vehicle/Camera.h"
|
|
#include "model/AnimModel.h"
|
|
#include "world/Event.h"
|
|
#include "world/EvLaunch.h"
|
|
#include "utilities/Timer.h"
|
|
#include "utilities/Logs.h"
|
|
#include "scene/sn_utils.h"
|
|
#include "rendering/renderer.h"
|
|
#include "widgets/map_objects.h"
|
|
|
|
namespace scene {
|
|
|
|
std::map<std::string, basic_node *> Hierarchy;
|
|
|
|
// 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, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 = Vehicle->VectorFront(); // wektor normalny dla płaszczyzny ruchu pantografu
|
|
auto const vUp = Vehicle->VectorUp(); // wektor pionu pudła (pochylony od pionu na przechyłce)
|
|
auto const vLeft = Vehicle->VectorLeft(); // wektor odległości w bok (odchylony od poziomu na przechyłce)
|
|
auto const position = 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
|
|
SetFlag( Vehicle->MoverParameters->DamageFlag, dtrain_pantograph );
|
|
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;
|
|
}
|
|
ErrorLog( "Bad traction: " + Vehicle->name() + " broke pantograph at " + to_string( pantographposition ), logtype::traction );
|
|
|
|
}
|
|
}
|
|
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 ) {
|
|
glm::dvec3 campos = Global.pCamera.Pos;
|
|
double radius = launcher->dRadius;
|
|
if (launcher->train_triggered && simulation::Train) {
|
|
campos = simulation::Train->Dynamic()->HeadPosition();
|
|
radius *= Timer::GetDeltaTime() * simulation::Train->Dynamic()->GetVelocity() * 0.277;
|
|
}
|
|
|
|
if( launcher->check_conditions()
|
|
&& ( radius < 0.0
|
|
|| glm::distance2( launcher->location(), campos ) < launcher->dRadius ) ) {
|
|
if( launcher->check_activation() )
|
|
launch_event( launcher, true );
|
|
if( launcher->check_activation_key() )
|
|
launch_event( launcher, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.radius() );
|
|
|
|
auto const &shapedata { Shape.data() };
|
|
auto &shapes = (
|
|
shapedata.translucent ?
|
|
m_shapestranslucent :
|
|
m_shapesopaque );
|
|
// if this cell's geometry was already baked (deferred visual streaming inserting into a
|
|
// section the renderer already finalised), don't try to merge into an existing shape
|
|
// whose CPU-side vertices were freed at bake time -- add the shape standalone and upload
|
|
// it straight into the live bank, otherwise it would never become visible.
|
|
if( m_geometrybank != null_handle ) {
|
|
Shape.origin( m_area.center );
|
|
shapes.emplace_back( Shape );
|
|
shapes.back().create_geometry( m_geometrybank );
|
|
return;
|
|
}
|
|
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)
|
|
// length2 is better than length for comparing because it does not require sqrt function
|
|
&& ( glm::length2( shapedata.area.center - targetshapedata.area.center ) < sq(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() };
|
|
// see the matching note in insert( shape_node ): once the cell is baked, append the new
|
|
// lines straight into the live bank rather than merging into vertex-freed geometry.
|
|
if( m_geometrybank != null_handle ) {
|
|
Lines.origin( m_area.center );
|
|
m_lines.emplace_back( Lines );
|
|
m_lines.back().create_geometry( m_geometrybank );
|
|
return;
|
|
}
|
|
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)
|
|
// length2 is better than length for comparing because it does not require sqrt function
|
|
&& ( glm::length2( linesdata.area.center - targetlinesdata.area.center ) < sq(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 );
|
|
// additionally route instanceable nodes into a per-(pModel, skins) bucket so
|
|
// the renderer can amortise material/state setup across many instances of the
|
|
// same TModel3d AND issue a single GPU-instanced draw call per submodel.
|
|
// Sub-bucketing on skin set ensures every batch has consistent textures —
|
|
// instances of the same pModel with different replacable skins land in
|
|
// different buckets and render via separate (correct) instanced draws.
|
|
if( Instance->m_instanceable && Instance->Model() != nullptr ) {
|
|
instance_bucket_key key;
|
|
key.pModel = Instance->Model();
|
|
auto const *mat = Instance->Material();
|
|
if( mat != nullptr ) {
|
|
for( int i = 0; i < 5; ++i ) { key.skins[i] = mat->replacable_skins[i]; }
|
|
}
|
|
m_instancebuckets_opaque[ key ].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 ) );
|
|
// also remove from the per-(pModel, skins) instance bucket if present
|
|
if( Instance->m_instanceable && Instance->Model() != nullptr ) {
|
|
instance_bucket_key key;
|
|
key.pModel = Instance->Model();
|
|
auto const *mat = Instance->Material();
|
|
if( mat != nullptr ) {
|
|
for( int i = 0; i < 5; ++i ) { key.skins[i] = mat->replacable_skins[i]; }
|
|
}
|
|
auto bucket = m_instancebuckets_opaque.find( key );
|
|
if( bucket != m_instancebuckets_opaque.end() ) {
|
|
bucket->second.erase(
|
|
std::remove( std::begin( bucket->second ), std::end( bucket->second ), Instance ),
|
|
std::end( bucket->second ) );
|
|
if( bucket->second.empty() ) {
|
|
m_instancebuckets_opaque.erase( bucket );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 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 {
|
|
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 ) {
|
|
|
|
// remember the bank for *all* cells (even ones empty at bake time): a deferred visual
|
|
// node may activate this cell later, and insert() needs the live bank to upload into.
|
|
m_geometrybank = 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
|
|
}
|
|
|
|
void basic_cell::create_map_geometry(std::vector<gfx::basic_vertex> &Bank, const gfx::geometrybank_handle Extra)
|
|
{
|
|
if (!m_active)
|
|
return;
|
|
|
|
for (auto *path : m_paths)
|
|
path->create_map_geometry(Bank, Extra);
|
|
}
|
|
|
|
void basic_cell::get_map_active_paths(map_colored_paths &handles)
|
|
{
|
|
for (auto *path : m_paths)
|
|
path->get_map_active_paths(handles);
|
|
}
|
|
|
|
glm::vec3 basic_cell::find_nearest_track_point(const glm::dvec3 &pos)
|
|
{
|
|
float min = std::numeric_limits<float>::max();
|
|
TTrack *nearest = nullptr;
|
|
glm::vec3 point;
|
|
|
|
for (auto *path : m_paths) {
|
|
glm::dvec3 ep = path->get_nearest_point(pos);
|
|
|
|
float dist2 = glm::distance2(ep, pos);
|
|
if (dist2 < min) {
|
|
point = ep;
|
|
min = dist2;
|
|
nearest = path;
|
|
}
|
|
}
|
|
|
|
if (!nearest)
|
|
return glm::vec3(NAN);
|
|
return point;
|
|
}
|
|
|
|
// executes event assigned to specified launcher
|
|
void
|
|
basic_cell::launch_event( TEventLauncher *Launcher, bool local_only ) {
|
|
WriteLog( "Eventlauncher: " + Launcher->name() );
|
|
if (!local_only) {
|
|
if( Launcher->Event1 ) {
|
|
simulation::Events.AddToQuery( Launcher->Event1, nullptr );
|
|
}
|
|
} else {
|
|
command_relay commandrelay;
|
|
if (Global.shiftState && Launcher->Event2 != nullptr)
|
|
commandrelay.post(user_command::queueevent, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &Launcher->Event2->name());
|
|
else if (Launcher->Event1)
|
|
commandrelay.post(user_command::queueevent, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &Launcher->Event1->name());
|
|
}
|
|
}
|
|
|
|
// 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 = Vehicle->VectorFront(); // wektor normalny dla płaszczyzny ruchu pantografu
|
|
auto const vUp = Vehicle->VectorUp(); // wektor pionu pudła (pochylony od pionu na przechyłce)
|
|
auto const vLeft = Vehicle->VectorLeft(); // wektor odległości w bok (odchylony od poziomu na przechyłce)
|
|
auto const position = 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 ) < sq(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 ) < sq(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 ) < sq(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 ) < sq(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 ) + Shape.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
|
|
// if the section was already baked (deferred visual streaming), append straight into
|
|
// the live bank instead of merging into vertex-freed geometry -- see basic_cell::insert.
|
|
if( true == m_geometrycreated ) {
|
|
Shape.origin( m_area.center );
|
|
m_shapes.emplace_back( Shape );
|
|
m_shapes.back().create_geometry( m_geometrybank );
|
|
return;
|
|
}
|
|
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 ) > sq(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 ) > sq(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 );
|
|
}
|
|
}
|
|
|
|
void basic_section::create_map_geometry(const gfx::geometrybank_handle handle)
|
|
{
|
|
std::vector<gfx::basic_vertex> lines;
|
|
gfx::userdata_array userdata{};
|
|
for (auto &cell : m_cells)
|
|
cell.create_map_geometry(lines, handle);
|
|
|
|
m_map_geometryhandle = GfxRenderer->Insert(lines, userdata, handle, GL_LINES);
|
|
}
|
|
|
|
void basic_section::get_map_active_paths(map_colored_paths &handles)
|
|
{
|
|
for (auto &cell : m_cells)
|
|
cell.get_map_active_paths(handles);
|
|
}
|
|
|
|
glm::vec3 basic_section::find_nearest_track_point(const glm::dvec3 &point)
|
|
{
|
|
glm::vec3 nearest(NAN);
|
|
float min = std::numeric_limits<float>::max();
|
|
|
|
for (int x = -1; x < 2; x++)
|
|
for (int y = -1; y < 2; y++) {
|
|
glm::vec3 p = cell(point, glm::ivec2(x, y)).find_nearest_track_point(point);
|
|
float dist2 = glm::distance2(p, (glm::vec3)point);
|
|
if (dist2 < min) {
|
|
min = dist2;
|
|
nearest = p;
|
|
}
|
|
}
|
|
|
|
return nearest;
|
|
}
|
|
|
|
// provides access to section enclosing specified point
|
|
basic_cell &
|
|
basic_section::cell( glm::dvec3 const &Location, const glm::ivec2 &offset ) {
|
|
|
|
auto const column = static_cast<int>( std::floor( ( Location.x - ( m_area.center.x - EU07_SECTIONSIZE / 2 ) ) / EU07_CELLSIZE ) ) + offset.x;
|
|
auto const row = static_cast<int>( std::floor( ( Location.z - ( m_area.center.z - EU07_SECTIONSIZE / 2 ) ) / EU07_CELLSIZE ) ) + offset.y;
|
|
|
|
return
|
|
m_cells[
|
|
std::clamp( row, 0, ( EU07_SECTIONSIZE / EU07_CELLSIZE ) - 1 ) * ( EU07_SECTIONSIZE / EU07_CELLSIZE )
|
|
+ std::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() {
|
|
|
|
if( false == simulation::is_ready ) { return; }
|
|
|
|
// 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 = Vehicle->VectorFront(); // wektor normalny dla płaszczyzny ruchu pantografu
|
|
auto const vUp = Vehicle->VectorUp(); // wektor pionu pudła (pochylony od pionu na przechyłce)
|
|
auto const vLeft = Vehicle->VectorLeft(); // wektor odległości w bok (odchylony od poziomu na przechyłce)
|
|
auto const position = 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 );
|
|
}
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> switchtrackbedtextures {
|
|
"rkpd34r190-tpd1",
|
|
"rkpd34r190-tpd2",
|
|
"rkpd34r190-tpd-oil2",
|
|
"rozkrz8r150-1pods-new",
|
|
"rozkrz8r150-2pods-new",
|
|
"rozkrz34r150-tpbps-new2",
|
|
"rozkrz34r150-tpd1",
|
|
"rz-1200-185",
|
|
"zwr41r500",
|
|
"zwrot-tpd-oil1",
|
|
"zwrot34r300pods",
|
|
"zwrot34r300pods-new",
|
|
"zwrot34r300pods-old",
|
|
"zwrotl65r1200pods-new",
|
|
"zwrotp65r1200pods-new" };
|
|
|
|
void
|
|
basic_region::insert( shape_node Shape, scratch_data &Scratchpad, bool const Transform ) {
|
|
|
|
if( Global.CreateSwitchTrackbeds ) {
|
|
|
|
auto const materialname{GfxRenderer->Material(Shape.data().material)->GetName()};
|
|
for( auto const &switchtrackbedtexture : switchtrackbedtextures ) {
|
|
if( contains( materialname, switchtrackbedtexture ) ) {
|
|
// geometry with blacklisted texture, part of old switch trackbed; ignore it
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// 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.invalidate_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 ) <= sq( 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[
|
|
std::clamp( row, 0, EU07_REGIONSIDESECTIONCOUNT - 1 ) * EU07_REGIONSIDESECTIONCOUNT
|
|
+ std::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;
|
|
}
|
|
|
|
void basic_region::create_map_geometry()
|
|
{
|
|
m_map_geometrybank = GfxRenderer->Create_Bank();
|
|
|
|
for (int row = 0; row < EU07_REGIONSIDESECTIONCOUNT; row++)
|
|
for (int column = 0; column < EU07_REGIONSIDESECTIONCOUNT; column++)
|
|
{
|
|
basic_section *s = m_sections[row * EU07_REGIONSIDESECTIONCOUNT + column];
|
|
if (s)
|
|
s->create_map_geometry(m_map_geometrybank);
|
|
}
|
|
}
|
|
|
|
void basic_region::update_poi_geometry()
|
|
{
|
|
std::vector<gfx::basic_vertex> vertices;
|
|
gfx::userdata_array userdata;
|
|
for (const auto &sem : map::Objects.entries)
|
|
vertices.push_back(std::move(sem->vertex()));
|
|
|
|
if (!m_map_poipoints) {
|
|
gfx::geometrybank_handle poibank = GfxRenderer->Create_Bank();
|
|
m_map_poipoints = GfxRenderer->Insert(vertices, userdata, poibank, GL_POINTS);
|
|
}
|
|
else {
|
|
GfxRenderer->Replace(vertices, userdata, m_map_poipoints, GL_POINTS);
|
|
}
|
|
}
|
|
|
|
} // scene
|
|
|
|
//---------------------------------------------------------------------------
|