/* 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 "drivermode.h" #include "driveruilayer.h" #include "Globals.h" #include "application.h" #include "simulation.h" #include "simulationtime.h" #include "simulationenvironment.h" #include "lightarray.h" #include "Train.h" #include "Driver.h" #include "DynObj.h" #include "Model3d.h" #include "Event.h" #include "messaging.h" #include "Timer.h" #include "renderer.h" #include "utilities.h" #include "Logs.h" /* namespace input { user_command command; // currently issued control command, if any } */ void driver_mode::drivermode_input::poll() { if (telemetry) telemetry->update(); keyboard.poll(); if( true == Global.InputMouse ) { mouse.poll(); } if( true == Global.InputGamepad ) { gamepad.poll(); } #ifdef WITH_UART if( uart != nullptr ) { uart->poll(); } #endif /* // TBD, TODO: wrap current command in object, include other input sources? input::command = ( mouse.command() != user_command::none ? mouse.command() : keyboard.command() ); */ } bool driver_mode::drivermode_input::init() { // initialize input devices auto result = ( keyboard.init() && mouse.init() ); if( true == Global.InputGamepad ) { gamepad.init(); } #ifdef WITH_UART if( true == Global.uart_conf.enable ) { uart = std::make_unique(); uart->init(); } #endif if (Global.motiontelemetry_conf.enable) telemetry = std::make_unique(); #ifdef _WIN32 Console::On(); // włączenie konsoli #endif return result; } driver_mode::driver_mode() { m_userinterface = std::make_shared(); } // initializes internal data structures of the mode. returns: true on success, false otherwise bool driver_mode::init() { return m_input.init(); } // mode-specific update of simulation data. returns: false on error, true otherwise bool driver_mode::update() { Timer::UpdateTimers(Global.iPause != 0); Timer::subsystem.sim_total.start(); double const deltatime = Timer::GetDeltaTime(); // 0.0 gdy pauza simulation::State.update_clocks(); simulation::Environment.update(); if (deltatime != 0.0) { // jak pauza, to nie ma po co tego przeliczać simulation::Time.update( deltatime ); // fixed step, simulation time based updates // m_primaryupdateaccumulator += dt; // unused for the time being m_secondaryupdateaccumulator += deltatime; /* // NOTE: until we have no physics state interpolation during render, we need to rely on the old code, // as doing fixed step calculations but flexible step render results in ugly mini jitter // core routines (physics) int updatecount = 0; while( ( m_primaryupdateaccumulator >= m_primaryupdaterate ) &&( updatecount < 20 ) ) { // no more than 20 updates per single pass, to keep physics from hogging up all run time Ground.Update( m_primaryupdaterate, 1 ); ++updatecount; m_primaryupdateaccumulator -= m_primaryupdaterate; } */ int updatecount = 1; if( deltatime > m_primaryupdaterate ) // normalnie 0.01s { /* // NOTE: experimentally disabled physics update cap auto const iterations = std::ceil(dt / m_primaryupdaterate); updatecount = std::min( 20, static_cast( iterations ) ); */ updatecount = std::ceil( deltatime / m_primaryupdaterate ); /* // NOTE: changing dt wrecks things further down the code. re-acquire proper value later or cleanup here dt = dt / iterations; // Ra: fizykę lepiej by było przeliczać ze stałym krokiem */ } auto const stepdeltatime { deltatime / updatecount }; // NOTE: updates are limited to 20, but dt is distributed over potentially many more iterations // this means at count > 20 simulation and render are going to desync. is that right? // NOTE: experimentally changing this to prevent the desync. // TODO: test what happens if we hit more than 20 * 0.01 sec slices, i.e. less than 5 fps Timer::subsystem.sim_dynamics.start(); if( true == Global.FullPhysics ) { // mixed calculation mode, steps calculated in ~0.05s chunks while( updatecount >= 5 ) { simulation::State.update( stepdeltatime, 5 ); updatecount -= 5; } if( updatecount ) { simulation::State.update( stepdeltatime, updatecount ); } } else { // simplified calculation mode; faster but can lead to errors simulation::State.update( stepdeltatime, updatecount ); } Timer::subsystem.sim_dynamics.stop(); // secondary fixed step simulation time routines while( m_secondaryupdateaccumulator >= m_secondaryupdaterate ) { // awaria PoKeys mogła włączyć pauzę - przekazać informację if( Global.iMultiplayer ) // dajemy znać do serwera o wykonaniu if( iPause != Global.iPause ) { // przesłanie informacji o pauzie do programu nadzorującego multiplayer::WyslijParam( 5, 3 ); // ramka 5 z czasem i stanem zapauzowania iPause = Global.iPause; } // TODO: generic shake update pass for vehicles within view range if( Camera.m_owner != nullptr ) { Camera.m_owner->update_shake( m_secondaryupdaterate ); } m_secondaryupdateaccumulator -= m_secondaryupdaterate; // these should be inexpensive enough we have no cap } // variable step simulation time routines if (!change_train.empty()) { TTrain *train = simulation::Trains.find(change_train); if (train) { simulation::Train = train; InOutKey(); m_relay.post(user_command::aidriverdisable, 0.0, 0.0, GLFW_PRESS, 0); change_train.clear(); } } if( ( simulation::Train == nullptr ) && ( false == FreeFlyModeFlag ) ) { // intercept cases when the driven train got removed after entering portal InOutKey(); } if (!FreeFlyModeFlag && simulation::Train->Dynamic() != Camera.m_owner) { // fixup camera after vehicle switch CabView(); } if( simulation::Train != nullptr ) TSubModel::iInstance = reinterpret_cast( simulation::Train->Dynamic() ); else TSubModel::iInstance = 0; simulation::Trains.update(deltatime); simulation::Events.update(); simulation::Region->update_events(); simulation::Lights.update(); } // render time routines follow: auto const deltarealtime = Timer::GetDeltaRenderTime(); // nie uwzględnia pauzowania ani mnożenia czasu simulation::State.process_commands(); // fixed step render time routines fTime50Hz += deltarealtime; // w pauzie też trzeba zliczać czas, bo przy dużym FPS będzie problem z odczytem ramek while( fTime50Hz >= 1.0 / 50.0 ) { #ifdef _WIN32 Console::Update(); // to i tak trzeba wywoływać #endif ui::Transcripts.Update(); // obiekt obsługujący stenogramy dźwięków na ekranie m_userinterface->update(); // decelerate camera Camera.Velocity *= 0.65; if( std::abs( Camera.Velocity.x ) < 0.01 ) { Camera.Velocity.x = 0.0; } if( std::abs( Camera.Velocity.y ) < 0.01 ) { Camera.Velocity.y = 0.0; } if( std::abs( Camera.Velocity.z ) < 0.01 ) { Camera.Velocity.z = 0.0; } // decelerate debug camera too DebugCamera.Velocity *= 0.65; if( std::abs( DebugCamera.Velocity.x ) < 0.01 ) { DebugCamera.Velocity.x = 0.0; } if( std::abs( DebugCamera.Velocity.y ) < 0.01 ) { DebugCamera.Velocity.y = 0.0; } if( std::abs( DebugCamera.Velocity.z ) < 0.01 ) { DebugCamera.Velocity.z = 0.0; } fTime50Hz -= 1.0 / 50.0; } // variable step render time routines update_camera( deltarealtime ); simulation::Environment.update_precipitation(); // has to be launched after camera step to work properly Timer::subsystem.sim_total.stop(); simulation::Region->update_sounds(); audio::renderer.update( Global.iPause ? 0.0 : deltarealtime ); GfxRenderer.Update( deltarealtime ); simulation::is_ready = true; return true; } // maintenance method, called when the mode is activated void driver_mode::enter() { TDynamicObject *nPlayerTrain { ( ( Global.local_start_vehicle != "ghostview" ) ? simulation::Vehicles.find( Global.local_start_vehicle ) : nullptr ) }; Camera.Init(Global.FreeCameraInit[0], Global.FreeCameraInitAngle[0], nullptr ); Global.pCamera = Camera; Global.pDebugCamera = DebugCamera; FreeFlyModeFlag = true; DebugCamera = Camera; if (nPlayerTrain) { WriteLog( "Trying to enter player train, \"" + Global.local_start_vehicle + "\"" ); m_relay.post(user_command::entervehicle, 0.0, 0.0, GLFW_PRESS, 0, nPlayerTrain->GetPosition()); change_train = nPlayerTrain->name(); } else if (Global.local_start_vehicle != "ghostview") { Error("Bad scenario: failed to locate player train, \"" + Global.local_start_vehicle + "\"" ); } // if (!Global.bMultiplayer) //na razie włączone { // eventy aktywowane z klawiatury tylko dla jednego użytkownika KeyEvents[ 0 ] = simulation::Events.FindEvent( "keyctrl00" ); KeyEvents[ 1 ] = simulation::Events.FindEvent( "keyctrl01" ); KeyEvents[ 2 ] = simulation::Events.FindEvent( "keyctrl02" ); KeyEvents[ 3 ] = simulation::Events.FindEvent( "keyctrl03" ); KeyEvents[ 4 ] = simulation::Events.FindEvent( "keyctrl04" ); KeyEvents[ 5 ] = simulation::Events.FindEvent( "keyctrl05" ); KeyEvents[ 6 ] = simulation::Events.FindEvent( "keyctrl06" ); KeyEvents[ 7 ] = simulation::Events.FindEvent( "keyctrl07" ); KeyEvents[ 8 ] = simulation::Events.FindEvent( "keyctrl08" ); KeyEvents[ 9 ] = simulation::Events.FindEvent( "keyctrl09" ); } Timer::ResetTimers(); set_picking( Global.ControlPicking ); } // maintenance method, called when the mode is deactivated void driver_mode::exit() { } void driver_mode::on_key( int const Key, int const Scancode, int const Action, int const Mods ) { Global.shiftState = ( Mods & GLFW_MOD_SHIFT ) ? true : false; Global.ctrlState = ( Mods & GLFW_MOD_CONTROL ) ? true : false; Global.altState = ( Mods & GLFW_MOD_ALT ) ? true : false; bool anyModifier = Mods & (GLFW_MOD_SHIFT | GLFW_MOD_CONTROL | GLFW_MOD_ALT); // give the ui first shot at the input processing... if( !anyModifier && true == m_userinterface->on_key( Key, Action ) ) { return; } // ...if the input is left untouched, pass it on if( true == m_input.keyboard.key( Key, Action ) ) { return; } if( ( true == Global.InputMouse ) && ( ( Key == GLFW_KEY_LEFT_ALT ) || ( Key == GLFW_KEY_RIGHT_ALT ) ) ) { // if the alt key was pressed toggle control picking mode and set matching cursor behaviour if( Action == GLFW_PRESS ) { // toggle picking mode set_picking( !Global.ControlPicking ); } } if( Action != GLFW_RELEASE ) { OnKeyDown( Key ); } } void driver_mode::on_cursor_pos( double const Horizontal, double const Vertical ) { // give the ui first shot at the input processing... if( true == m_userinterface->on_cursor_pos( Horizontal, Vertical ) ) { return; } if( false == Global.ControlPicking ) { // in regular view mode keep cursor on screen Application.set_cursor_pos( 0, 0 ); } // give the potential event recipient a shot at it, in the virtual z order m_input.mouse.move( Horizontal, Vertical ); } void driver_mode::on_mouse_button( int const Button, int const Action, int const Mods ) { // give the ui first shot at the input processing... if( true == m_userinterface->on_mouse_button( Button, Action ) ) { return; } // give the potential event recipient a shot at it, in the virtual z order m_input.mouse.button( Button, Action ); } void driver_mode::on_scroll( double const Xoffset, double const Yoffset ) { m_input.mouse.scroll( Xoffset, Yoffset ); } void driver_mode::on_event_poll() { m_input.poll(); } bool driver_mode::is_command_processor() { return true; } void driver_mode::update_camera( double const Deltatime ) { auto *controlled = ( simulation::Train ? simulation::Train->Dynamic() : nullptr ); if( false == Global.ControlPicking ) { if( m_input.mouse.button( GLFW_MOUSE_BUTTON_LEFT ) == GLFW_PRESS ) { Camera.Reset(); // likwidacja obrotów - patrzy horyzontalnie na południe if( Camera.m_owner == nullptr ) { if( controlled && LengthSquared3( controlled->GetPosition() - Camera.Pos ) < ( 1500 * 1500 ) ) { // gdy bliżej niż 1.5km Camera.LookAt = controlled->GetPosition(); } else { TDynamicObject *d = std::get( simulation::Region->find_vehicle( Global.pCamera.Pos, 300, false, false ) ); if( !d ) d = std::get( simulation::Region->find_vehicle( Global.pCamera.Pos, 1000, false, false ) ); // dalej szukanie, jesli bliżej nie ma if( d && pDynamicNearest ) { // jeśli jakiś jest znaleziony wcześniej if( 100.0 * LengthSquared3( d->GetPosition() - Camera.Pos ) > LengthSquared3( pDynamicNearest->GetPosition() - Camera.Pos ) ) { d = pDynamicNearest; // jeśli najbliższy nie jest 10 razy bliżej niż } } // poprzedni najbliższy, zostaje poprzedni if( d ) pDynamicNearest = d; // zmiana na nowy, jeśli coś znaleziony niepusty if( pDynamicNearest ) Camera.LookAt = pDynamicNearest->GetPosition(); } Camera.RaLook(); // jednorazowe przestawienie kamery } else { if( false == FreeFlyModeFlag ) { // reset cached view angle in the cab simulation::Train->pMechViewAngle = { Camera.Angle.x, Camera.Angle.y }; } } } else if( m_input.mouse.button( GLFW_MOUSE_BUTTON_RIGHT ) == GLFW_PRESS ) { CabView(); } } if( m_input.mouse.button( GLFW_MOUSE_BUTTON_MIDDLE ) == GLFW_PRESS ) { // middle mouse button controls zoom. Global.ZoomFactor = std::min( 4.5f, Global.ZoomFactor + 15.0f * static_cast( Deltatime ) ); } else if( m_input.mouse.button( GLFW_MOUSE_BUTTON_MIDDLE ) != GLFW_PRESS ) { // reset zoom level if the button is no longer held down. // NOTE: yes, this is terrible way to go about it. it'll do for now. Global.ZoomFactor = std::max( 1.0f, Global.ZoomFactor - 15.0f * static_cast( Deltatime ) ); } // uwzględnienie ruchu wywołanego klawiszami if( false == DebugCameraFlag ) { // regular camera if( ( false == FreeFlyModeFlag ) && ( false == Global.CabWindowOpen ) ) { // if in cab potentially alter camera placement based on changes in train object Camera.m_owneroffset = simulation::Train->pMechOffset; Camera.Angle.x = simulation::Train->pMechViewAngle.x; Camera.Angle.y = simulation::Train->pMechViewAngle.y; } Camera.Update(); if( false == FreeFlyModeFlag ) { // keep the camera within cab boundaries Camera.m_owneroffset = simulation::Train->clamp_inside( Camera.m_owneroffset ); } if( ( false == FreeFlyModeFlag ) && ( false == Global.CabWindowOpen ) ) { // cache cab camera in case of view type switch simulation::Train->pMechViewAngle = { Camera.Angle.x, Camera.Angle.y }; simulation::Train->pMechOffset = Camera.m_owneroffset; } if( ( true == FreeFlyModeFlag ) && ( Camera.m_owner != nullptr ) ) { // cache external view config auto &externalviewconfig { m_externalviewconfigs[ m_externalviewmode ] }; externalviewconfig.owner = Camera.m_owner; externalviewconfig.offset = Camera.m_owneroffset; externalviewconfig.angle = Camera.Angle; } } else { // debug camera DebugCamera.Update(); } // reset window state, it'll be set again if applicable in a check below Global.CabWindowOpen = false; if( ( simulation::Train != nullptr ) && ( Camera.m_owner != nullptr ) && ( false == DebugCameraFlag ) ) { // jeśli jazda w kabinie, przeliczyć trzeba parametry kamery /* auto tempangle = controlled->VectorFront() * ( controlled->MoverParameters->ActiveCab == -1 ? -1 : 1 ); double modelrotate = atan2( -tempangle.x, tempangle.z ); */ if( ( false == FreeFlyModeFlag ) && ( true == Global.ctrlState ) && ( ( m_input.keyboard.key( GLFW_KEY_LEFT ) != GLFW_RELEASE ) || ( m_input.keyboard.key( GLFW_KEY_RIGHT ) != GLFW_RELEASE ) ) ) { // jeśli lusterko lewe albo prawe (bez rzucania na razie) Global.CabWindowOpen = true; auto const lr { m_input.keyboard.key( GLFW_KEY_LEFT ) != GLFW_RELEASE }; // Camera.Yaw powinno być wyzerowane, aby po powrocie patrzeć do przodu Camera.Pos = controlled->GetPosition() + simulation::Train->MirrorPosition( lr ); // pozycja lusterka Camera.Angle.y = 0; // odchylenie na bok od Camera.LookAt if( simulation::Train->Occupied()->ActiveCab == 0 ) { // gdy w korytarzu Camera.LookAt = Camera.Pos - simulation::Train->GetDirection(); } else if( Global.shiftState ) { // patrzenie w bok przez szybę Camera.LookAt = Camera.Pos - ( lr ? -1 : 1 ) * controlled->VectorLeft() * simulation::Train->Occupied()->ActiveCab; } else { // patrzenie w kierunku osi pojazdu, z uwzględnieniem kabiny - jakby z lusterka, // ale bez odbicia Camera.LookAt = Camera.Pos - simulation::Train->GetDirection() * simulation::Train->Occupied()->ActiveCab; //-1 albo 1 } auto const shakeangles { simulation::Train->Dynamic()->shake_angles() }; Camera.Angle.x = 0.5 * shakeangles.second; // hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki /* Camera.Roll = std::atan( simulation::Train->pMechShake.x * simulation::Train->BaseShake.angle_scale.x ); // hustanie kamery na boki Camera.Pitch = 0.5 * std::atan( simulation::Train->vMechVelocity.z * simulation::Train->BaseShake.angle_scale.z ); // hustanie kamery przod tyl */ Camera.vUp = controlled->VectorUp(); } else { // patrzenie standardowe if( false == FreeFlyModeFlag ) { // potentially restore view angle after returning from external view // TODO: mirror view toggle as separate method Camera.Angle.x = simulation::Train->pMechViewAngle.x; Camera.Angle.y = simulation::Train->pMechViewAngle.y; } auto const shakescale { FreeFlyModeFlag ? 5.0 : 1.0 }; auto shakencamerapos { Camera.m_owneroffset + shakescale * Math3D::vector3( 1.5 * Camera.m_owner->ShakeState.offset.x, 2.0 * Camera.m_owner->ShakeState.offset.y, 1.5 * Camera.m_owner->ShakeState.offset.z ) }; Camera.Pos = ( Camera.m_owner->GetWorldPosition ( FreeFlyModeFlag ? shakencamerapos : // TODO: vehicle collision box for the external vehicle camera simulation::Train->clamp_inside( shakencamerapos ) ) ); if( !Global.iPause ) { // podczas pauzy nie przeliczać kątów przypadkowymi wartościami auto const shakeangles { Camera.m_owner->shake_angles() }; Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki /* Camera.Roll = std::atan( simulation::Train->vMechVelocity.x * simulation::Train->BaseShake.angle_scale.x ); // hustanie kamery na boki Camera.Pitch -= 0.5 * atan( simulation::Train->vMechVelocity.z * simulation::Train->BaseShake.angle_scale.z ); // hustanie kamery przod tyl */ } /* // ABu011104: rzucanie pudlem vector3 temp; if( abs( Train->pMechShake.y ) < 0.25 ) temp = vector3( 0, 0, 6 * Train->pMechShake.y ); else if( ( Train->pMechShake.y ) > 0 ) temp = vector3( 0, 0, 6 * 0.25 ); else temp = vector3( 0, 0, -6 * 0.25 ); if( Controlled ) Controlled->ABuSetModelShake( temp ); // ABu: koniec rzucania */ if( simulation::Train->Occupied()->ActiveCab == 0 ) { // gdy w korytarzu Camera.LookAt = Camera.m_owner->GetWorldPosition( Camera.m_owneroffset ) + Camera.m_owner->VectorFront() * 5.0; } else { // patrzenie w kierunku osi pojazdu, z uwzględnieniem kabiny Camera.LookAt = Camera.m_owner->GetWorldPosition( Camera.m_owneroffset ) + Camera.m_owner->VectorFront() * 5.0 * simulation::Train->Occupied()->ActiveCab; //-1 albo 1 } Camera.vUp = simulation::Train->GetUp(); } } // all done, update camera position to the new value Global.pCamera = Camera; } void driver_mode::OnKeyDown(int cKey) { // dump keypress info in the log // podczas pauzy klawisze nie działają std::string keyinfo; auto keyname = glfwGetKeyName( cKey, 0 ); if( keyname != nullptr ) { keyinfo += std::string( keyname ); } else { switch( cKey ) { case GLFW_KEY_SPACE: { keyinfo += "Space"; break; } case GLFW_KEY_ENTER: { keyinfo += "Enter"; break; } case GLFW_KEY_ESCAPE: { keyinfo += "Esc"; break; } case GLFW_KEY_TAB: { keyinfo += "Tab"; break; } case GLFW_KEY_INSERT: { keyinfo += "Insert"; break; } case GLFW_KEY_DELETE: { keyinfo += "Delete"; break; } case GLFW_KEY_HOME: { keyinfo += "Home"; break; } case GLFW_KEY_END: { keyinfo += "End"; break; } case GLFW_KEY_F1: { keyinfo += "F1"; break; } case GLFW_KEY_F2: { keyinfo += "F2"; break; } case GLFW_KEY_F3: { keyinfo += "F3"; break; } case GLFW_KEY_F4: { keyinfo += "F4"; break; } case GLFW_KEY_F5: { keyinfo += "F5"; break; } case GLFW_KEY_F6: { keyinfo += "F6"; break; } case GLFW_KEY_F7: { keyinfo += "F7"; break; } case GLFW_KEY_F8: { keyinfo += "F8"; break; } case GLFW_KEY_F9: { keyinfo += "F9"; break; } case GLFW_KEY_F10: { keyinfo += "F10"; break; } case GLFW_KEY_F11: { keyinfo += "F11"; break; } case GLFW_KEY_F12: { keyinfo += "F12"; break; } case GLFW_KEY_PAUSE: { keyinfo += "Pause"; break; } } } if( keyinfo.empty() == false ) { std::string keymodifiers; if( Global.shiftState ) keymodifiers += "[Shift]+"; if( Global.ctrlState ) keymodifiers += "[Ctrl]+"; WriteLog( "Key pressed: " + keymodifiers + "[" + keyinfo + "]" ); } // actual key processing // TODO: redo the input system if( ( cKey >= GLFW_KEY_0 ) && ( cKey <= GLFW_KEY_9 ) ) { // klawisze cyfrowe int i = cKey - GLFW_KEY_0; // numer klawisza if (Global.shiftState) { // z [Shift] uruchomienie eventu if( ( false == Global.iPause ) // podczas pauzy klawisze nie działają && ( KeyEvents[ i ] != nullptr ) ) { m_relay.post(user_command::queueevent, (double)simulation::Events.GetEventId(KeyEvents[i]), 0.0, GLFW_PRESS, 0); } } else if( Global.ctrlState ) { // zapamiętywanie kamery może działać podczas pauzy if( FreeFlyModeFlag ) { // w trybie latania można przeskakiwać do ustawionych kamer if( ( Global.FreeCameraInit[ i ].x == 0.0 ) && ( Global.FreeCameraInit[ i ].y == 0.0 ) && ( Global.FreeCameraInit[ i ].z == 0.0 ) ) { // jeśli kamera jest w punkcie zerowym, zapamiętanie współrzędnych i kątów Global.FreeCameraInit[ i ] = Camera.Pos; Global.FreeCameraInitAngle[ i ] = Camera.Angle; // logowanie, żeby można było do scenerii przepisać WriteLog( "camera " + std::to_string( Global.FreeCameraInit[ i ].x ) + " " + std::to_string( Global.FreeCameraInit[ i ].y ) + " " + std::to_string( Global.FreeCameraInit[ i ].z ) + " " + std::to_string( RadToDeg( Global.FreeCameraInitAngle[ i ].x ) ) + " " + std::to_string( RadToDeg( Global.FreeCameraInitAngle[ i ].y ) ) + " " + std::to_string( RadToDeg( Global.FreeCameraInitAngle[ i ].z ) ) + " " + std::to_string( i ) + " endcamera" ); } else // również przeskakiwanie { // Ra: to z tą kamerą (Camera.Pos i Global.pCameraPosition) jest trochę bez sensu Global.pCamera.Pos = Global.FreeCameraInit[ i ]; // nowa pozycja dla generowania obiektów Camera.Init( Global.FreeCameraInit[ i ], Global.FreeCameraInitAngle[ i ], nullptr ); // przestawienie } } } return; } switch (cKey) { case GLFW_KEY_F4: { if( Global.shiftState ) { ExternalView(); } // with Shift, cycle through external views else { InOutKey(); } // without, step out of the cab or return to it break; } case GLFW_KEY_F5: { // przesiadka do innego pojazdu if (!FreeFlyModeFlag) // only available in free fly mode break; TDynamicObject *dynamic = std::get( simulation::Region->find_vehicle( Camera.Pos, 50, true, false ) ); if (dynamic) { m_relay.post(user_command::entervehicle, 0.0, 0.0, GLFW_PRESS, 0); change_train = dynamic->name(); } break; } case GLFW_KEY_F6: { // przyspieszenie symulacji do testowania scenerii... uwaga na FPS! if( DebugModeFlag ) { if( Global.ctrlState ) { Global.fTimeSpeed = ( Global.shiftState ? 60.0 : 20.0 ); } else { Global.fTimeSpeed = ( Global.shiftState ? 5.0 : Global.default_timespeed ); } } break; } case GLFW_KEY_F7: { // debug mode functions if( DebugModeFlag ) { if( Global.ctrlState && Global.shiftState ) { // shift + ctrl + f7 toggles between debug and regular camera DebugCameraFlag = !DebugCameraFlag; } else if( Global.ctrlState ) { // ctrl + f7 toggles static daylight simulation::Environment.toggle_daylight(); break; } else if( Global.shiftState ) { // shift + f7 is currently unused } else { // f7: wireframe toggle Global.bWireFrame = !Global.bWireFrame; } } break; } case GLFW_KEY_F11: { // editor mode if( ( false == Global.ctrlState ) && ( false == Global.shiftState ) ) { Application.push_mode( eu07_application::mode::editor ); } break; } default: { break; } } return; // nie są przekazywane do pojazdu wcale } // places camera outside the controlled vehicle, or nearest if nothing is under control // depending on provided switch the view is placed right outside, or at medium distance void driver_mode::DistantView( bool const Near ) { TDynamicObject const *vehicle = { ( ( simulation::Train != nullptr ) ? simulation::Train->Dynamic() : pDynamicNearest ) }; if( vehicle == nullptr ) { return; } auto const cab = ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ); auto const left = vehicle->VectorLeft() * cab; if( true == Near ) { Camera.Pos = Math3D::vector3( Camera.Pos.x, vehicle->GetPosition().y, Camera.Pos.z ) + left * vehicle->GetWidth() + Math3D::vector3( 1.25 * left.x, 1.6, 1.25 * left.z ); } else { Camera.Pos = vehicle->GetPosition() + vehicle->VectorFront() * vehicle->MoverParameters->ActiveCab * 50.0 + Math3D::vector3( -10.0 * left.x, 1.6, -10.0 * left.z ); } Camera.m_owner = nullptr; Camera.LookAt = vehicle->GetPosition(); Camera.RaLook(); // jednorazowe przestawienie kamery } void driver_mode::ExternalView() { auto *train { simulation::Train }; if( train == nullptr ) { return; } auto *vehicle { train->Dynamic() }; // disable detailed cab in external view modes vehicle->bDisplayCab = false; if( true == m_externalview ) { // we're already in some external view mode, so select next one on the list m_externalviewmode = clamp_circular( ++m_externalviewmode, static_cast( view::count_ ) ); } FreeFlyModeFlag = true; m_externalview = true; Camera.Reset(); // configure camera placement for the selected view mode switch( m_externalviewmode ) { case view::consistfront: { // bind camera with the vehicle auto *owner { vehicle->Mechanik->Vehicle( end::front ) }; Camera.m_owner = owner; auto const &viewconfig { m_externalviewconfigs[ m_externalviewmode ] }; if( owner == viewconfig.owner ) { // restore view config for previous owner Camera.m_owneroffset = viewconfig.offset; Camera.Angle = viewconfig.angle; } else { // default view setup auto const offsetflip{ ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ) * ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) }; Camera.m_owneroffset = { 1.5 * owner->MoverParameters->Dim.W * offsetflip, std::max( 5.0, 1.25 * owner->MoverParameters->Dim.H ), -0.4 * owner->MoverParameters->Dim.L * offsetflip }; Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 180.0 : 0.0 ) ); } auto const shakeangles{ owner->shake_angles() }; Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki break; } case view::consistrear: { // bind camera with the vehicle auto *owner { vehicle->Mechanik->Vehicle( end::rear ) }; Camera.m_owner = owner; auto const &viewconfig{ m_externalviewconfigs[ m_externalviewmode ] }; if( owner == viewconfig.owner ) { // restore view config for previous owner Camera.m_owneroffset = viewconfig.offset; Camera.Angle = viewconfig.angle; } else { // default view setup auto const offsetflip{ ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ) * ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) * -1 }; Camera.m_owneroffset = { 1.5 * owner->MoverParameters->Dim.W * offsetflip, std::max( 5.0, 1.25 * owner->MoverParameters->Dim.H ), 0.2 * owner->MoverParameters->Dim.L * offsetflip }; Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 0.0 : 180.0 ) ); } auto const shakeangles { owner->shake_angles() }; Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki break; } case view::bogie: { auto *owner { vehicle->Mechanik->Vehicle( end::front ) }; Camera.m_owner = owner; auto const &viewconfig{ m_externalviewconfigs[ m_externalviewmode ] }; if( owner == viewconfig.owner ) { // restore view config for previous owner Camera.m_owneroffset = viewconfig.offset; Camera.Angle = viewconfig.angle; } else { // default view setup auto const offsetflip{ ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ) * ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) }; Camera.m_owneroffset = { -0.65 * owner->MoverParameters->Dim.W * offsetflip, 0.90, 0.15 * owner->MoverParameters->Dim.L * offsetflip }; Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 180.0 : 0.0 ) ); } auto const shakeangles { owner->shake_angles() }; Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki break; } case view::driveby: { DistantView( false ); break; } default: { break; } } } // ustawienie śledzenia pojazdu void driver_mode::CabView() { // TODO: configure owner and camera placement depending on the view mode if( true == FreeFlyModeFlag ) { return; } auto *train { simulation::Train }; if( train == nullptr ) { return; } m_externalview = false; // likwidacja obrotów - patrzy horyzontalnie na południe Camera.Reset(); // bind camera with the vehicle Camera.m_owner = train->Dynamic(); // potentially restore cached camera setup Camera.m_owneroffset = train->pMechSittingPosition; Camera.Angle.x = train->pMechViewAngle.x; Camera.Angle.y = train->pMechViewAngle.y; auto const shakeangles { Camera.m_owner->shake_angles() }; Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki if( train->Occupied()->ActiveCab == 0 ) { Camera.LookAt = Camera.m_owner->GetWorldPosition( Camera.m_owneroffset ) + Camera.m_owner->VectorFront() * 5.0; } else { // patrz w strone wlasciwej kabiny Camera.LookAt = Camera.m_owner->GetWorldPosition( Camera.m_owneroffset ) + Camera.m_owner->VectorFront() * 5.0 * Camera.m_owner->MoverParameters->ActiveCab; } train->pMechOffset = Camera.m_owneroffset; } void driver_mode::InOutKey() { // przełączenie widoku z kabiny na zewnętrzny i odwrotnie FreeFlyModeFlag = !FreeFlyModeFlag; // zmiana widoku auto *train { simulation::Train }; if( train == nullptr ) { FreeFlyModeFlag = true; // nadal poza kabiną Camera.m_owner = nullptr; // detach camera from the vehicle return; } auto *vehicle { train->Dynamic() }; if (FreeFlyModeFlag) { // jeżeli poza kabiną, przestawiamy w jej okolicę - OK // cache current cab position so there's no need to set it all over again after each out-in switch train->pMechSittingPosition = train->pMechOffset; vehicle->bDisplayCab = false; DistantView( true ); DebugCamera = Camera; } else { // jazda w kabinie // zerowanie przesunięcia przed powrotem? vehicle->ABuSetModelShake( { 0, 0, 0 } ); vehicle->bDisplayCab = true; CabView(); // na pozycję mecha } // update window title to reflect the situation Application.set_title( Global.AppName + " (" + ( train != nullptr ? train->Occupied()->Name : "" ) + " @ " + Global.SceneryFile + ")" ); } void driver_mode::set_picking( bool const Picking ) { if( Picking ) { // enter picking mode Application.set_cursor_pos( m_input.mouse_pickmodepos.x, m_input.mouse_pickmodepos.y ); Application.set_cursor( GLFW_CURSOR_NORMAL ); } else { // switch off Application.get_cursor_pos( m_input.mouse_pickmodepos.x, m_input.mouse_pickmodepos.y ); Application.set_cursor( GLFW_CURSOR_DISABLED ); Application.set_cursor_pos( 0, 0 ); } // actually toggle the mode Global.ControlPicking = Picking; }