/* 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 "application/drivermode.h" #include "application/driveruilayer.h" #include "utilities/Globals.h" #include "application/application.h" #include "utilities/translation.h" #include "simulation/simulation.h" #include "simulation/simulationtime.h" #include "simulation/simulationenvironment.h" #include "scene/scene.h" #include "rendering/lightarray.h" #include "rendering/particles.h" #include "vehicle/Train.h" #include "vehicle/Driver.h" #include "vehicle/DynObj.h" #include "model/Model3d.h" #include "world/Event.h" #include "input/messaging.h" #include "utilities/Timer.h" #include "rendering/renderer.h" #include "utilities/utilities.h" #include "utilities/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 #ifdef WITH_ZMQ if (zmq != nullptr) { zmq->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 #ifdef WITH_ZMQ if (!Global.zmq_address.empty()) { zmq = std::make_unique(); } #endif if (Global.motiontelemetry_conf.enable) telemetry = std::make_unique(); #ifdef _WIN32 Console::On(); // włączenie konsoli #endif return result; } std::string driver_mode::drivermode_input::binding_hints(std::pair const &Commands) const { auto const inputhintleft{keyboard.binding_hint(Commands.first)}; auto const inputhintright{keyboard.binding_hint(Commands.second)}; std::string inputhints = inputhintleft + (inputhintright.empty() ? "" : inputhintleft.empty() ? inputhintright : "] [" + inputhintright); return inputhints; } std::unordered_map> commandfallbacks = { {user_command::mastercontrollerset, {user_command::mastercontrollerincrease, user_command::mastercontrollerdecrease}}, {user_command::secondcontrollerset, {user_command::secondcontrollerincrease, user_command::secondcontrollerdecrease}}, {user_command::trainbrakeset, {user_command::trainbrakeincrease, user_command::trainbrakedecrease}}, {user_command::independentbrakeset, {user_command::independentbrakeincrease, user_command::independentbrakedecrease}}, {user_command::linebreakeropen, {user_command::linebreakertoggle, user_command::none}}, {user_command::linebreakerclose, {user_command::linebreakertoggle, user_command::none}}, {user_command::pantographlowerfront, {user_command::pantographtogglefront, user_command::none}}, {user_command::pantographlowerrear, {user_command::pantographtogglerear, user_command::none}}, {user_command::compartmentlightsenable, {user_command::compartmentlightstoggle, user_command::none}}, {user_command::compartmentlightsdisable, {user_command::compartmentlightstoggle, user_command::none}}, {user_command::batteryenable, {user_command::batterytoggle, user_command::none}}, {user_command::batterydisable, {user_command::batterytoggle, user_command::none}}, }; std::pair driver_mode::drivermode_input::command_fallback(user_command const Command) const { if (Command == user_command::none) { return {user_command::none, user_command::none}; } auto const lookup{commandfallbacks.find(Command)}; if (lookup == commandfallbacks.end()) { return {user_command::none, user_command::none}; } return lookup->second; } 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::State.update_scripting_interface(); 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) { Global.local_start_vehicle = change_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; if (Global.trainThreads > 0) simulation::Trains.updateAsync(deltatime); else 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 bool runonce{false}; 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; } if (false == runonce) { // tooltip update set_tooltip(""); auto const *train{simulation::Train}; if ((train != nullptr) && (false == FreeFlyModeFlag)) { if (false == DebugModeFlag) { // in regular mode show control functions, for defined controls auto const controlname{train->GetLabel(GfxRenderer->Pick_Control())}; if (false == controlname.empty()) { auto const mousecommands{m_input.mouse.bindings(controlname)}; auto inputhints{m_input.binding_hints(mousecommands)}; // if the commands bound with the control don't have any assigned keys try potential fallbacks if (inputhints.empty()) { inputhints = m_input.binding_hints(m_input.command_fallback(mousecommands.first)); } if (inputhints.empty()) { inputhints = m_input.binding_hints(m_input.command_fallback(mousecommands.second)); } // ready or not, here we go if (inputhints.empty()) { set_tooltip(Translations.label_cab_control(controlname)); } else { set_tooltip(Translations.label_cab_control(controlname) + " [" + inputhints + "]"); } } } else { // in debug mode show names of submodels, to help with cab setup and/or debugging auto const cabcontrol = GfxRenderer->Pick_Control(); set_tooltip((cabcontrol ? cabcontrol->pName : "")); } } if (Global.ControlPicking && FreeFlyModeFlag && DebugModeFlag) { const auto sceneryNode = GfxRenderer->Pick_Node(); const std::string content = sceneryNode ? sceneryNode->tooltip() : ""; set_tooltip(content); } runonce = true; } 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); // NOTE: particle system runs on simulation time, but needs actual camera position to determine how to update each particle source simulation::Particles.update(); GfxRenderer->Update(deltarealtime); simulation::is_ready = simulation::is_ready || ((simulation::Train != nullptr) && (simulation::Train->is_cab_initialized)) || (Global.local_start_vehicle == "ghostview"); 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(), &Global.local_start_vehicle); change_train = nPlayerTrain->name(); } else if (Global.local_start_vehicle != "ghostview") { 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.captureonstart); } // 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) { #ifndef __unix__ Global.shiftState = (Mods & GLFW_MOD_SHIFT) ? true : false; Global.ctrlState = (Mods & GLFW_MOD_CONTROL) ? true : false; Global.altState = (Mods & GLFW_MOD_ALT) ? true : false; #endif 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 (Key == (GLFW_KEY_F12) && Global.shiftState && Action == GLFW_PRESS) { m_userinterface->showDebugUI(); 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() const { 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() + 0.4 * controlled->VectorUp() * controlled->MoverParameters->Dim.H; } else { TDynamicObject *d = std::get(simulation::Region->find_vehicle(Camera.Pos, 300, false, false)); if (!d) d = std::get(simulation::Region->find_vehicle(Camera.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() + 0.4 * pDynamicNearest->VectorUp() * pDynamicNearest->MoverParameters->Dim.H; } 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 ((simulation::Train != nullptr) && (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 ((simulation::Train != nullptr) && (false == FreeFlyModeFlag)) { // keep the camera within cab boundaries Camera.m_owneroffset = simulation::Train->clamp_inside(Camera.m_owneroffset); } if ((simulation::Train != nullptr) && (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->CabOccupied == -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()->CabOccupied == 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()->CabOccupied; } 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()->CabOccupied; //-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()->CabOccupied == 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()->CabOccupied; //-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 ((Global.iPause == 0) // podczas pauzy klawisze nie działają && (KeyEvents[i] != nullptr)) { m_relay.post(user_command::queueevent, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &KeyEvents[i]->name()); } } 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(Global.pCamera.Pos, 50, false, false)); if (dynamic) { m_relay.post(user_command::entervehicle, (Global.ctrlState ? GLFW_MOD_CONTROL : 0), (simulation::Train ? simulation::Train->id() : 0), GLFW_PRESS, 0, dynamic->GetPosition(), &dynamic->name()); 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 Global.FakeLight = !Global.FakeLight; simulation::Environment.on_daylight_change(); 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->CabOccupied == 0 ? 1 : vehicle->MoverParameters->CabOccupied); 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->CabOccupied * 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->CabOccupied == 0 ? 1 : vehicle->MoverParameters->CabOccupied) * (vehicle->MoverParameters->DirActive == 0 ? 1 : vehicle->MoverParameters->DirActive)}; 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->DirActive < 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->CabOccupied == 0 ? 1 : vehicle->MoverParameters->CabOccupied) * (vehicle->MoverParameters->DirActive == 0 ? 1 : vehicle->MoverParameters->DirActive) * -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->DirActive < 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->CabOccupied == 0 ? 1 : vehicle->MoverParameters->CabOccupied) * (vehicle->MoverParameters->DirActive == 0 ? 1 : vehicle->MoverParameters->DirActive)}; 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->DirActive < 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()->CabOccupied == 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->CabOccupied; } 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 m_input.mouse_pickmodepos = glm::dvec2(Global.cursor_pos); Application.set_cursor(GLFW_CURSOR_DISABLED); Application.set_cursor_pos(0, 0); } // actually toggle the mode Global.ControlPicking = Picking; }