mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 12:09:19 +02:00
Today's work on the binary scene format plan (binary-scene-format-plan.md), from the first measurement to a terrain that bakes itself on first run and streams from a thread. The step-by-step history is kept on backup/scene-streaming-format-presquash-0916. Measuring first. -loadprofile times a scenery load by stage and by node kind and reports node counts and resident memory. On l204 terrain is 95% of the bytes and 99.8% of the nodes; on quarkmce2007 vehicles, not models, dominate what is left. The terrain turned out to be a heightfield in all but name, a ribbon covering 12% of its bounding box. The heightfield. scene/heightfieldformat.h is the .ehf layout: Morton-ordered tiles of 128 samples with one shared edge sample, a point-sampled mip chain, zstd per tile, a nodata sentinel, a material palette with a measured tiling per material, and a stamp of the rules that decided what counted as terrain. scene/heightfieldreader.h reads it for the tool, the renderer and the loader alike, and scene/terraincooker.h writes it, shared by the terraincook tool and the engine so a first-run bake is the file the tool would have made. The cooker rasterises every triangle where it is drawn (through scene::place_in_world, the placement the region applies too), keeps an upper surface laid within half a metre over another and the lower of two further apart, and then asks each triangle whether the heightfield shows it: laid, reaching a sample, keeping nearly all it claimed, and with the ground around it drawn at every level out to 2 km. Baking on first run. simulation/terrainbake bakes <scenario>.ehf the first time a scenery is opened, choosing the grid step from the first 200,000 vertices, and on later loads leaves out the triangles its node table says the heightfield shows. Everything else - trees, grass tufts, gantries, bridge decks - is drawn as written, so a bad bake can at worst draw something twice, and the heightfield sits 2 cm under the triangles so the original wins there. Nodes are keyed by file, line, include parameters and placement; an edited file changes its keys and those nodes are drawn again. A bake under older rules, or without its table, is thrown away and redone. A launcher copy named $scenario shares the scenario's files. -nobaketerrain shows the triangles as written. A scenery can still name hand-cooked fields, several at different steps. Drawing and streaming. rendering/terrainclipmap draws resident tiles at the mip level their distance earns, deriving the grid from gl_VertexID and texturing in world space with the scenery's own materials. rendering/terraintileloader reads and decodes tiles on a thread of its own; the renderer rescans what is in range only after moving a quarter of a tile, asks for what is missing nearest first, uploads at most 32 tiles a frame, drops arrivals the camera has moved past, and keeps drawing a tile at the level it has until the right one arrives. The terrain has its own range, terrainrange in the ini and a slider in the debug panel, 20 km by default and independent of basedrawrange; the far plane of an ordinary view is pushed out to it. The debug panel shows the range, tiles in view, on the gpu, wanted and queued, and how many sit at each level. Events. The event queue moved out of the events into a binary heap with a deterministic tie-break, and an event is handed its activator instead of storing it, so basic_event is a definition and nothing else. The logic container. scene/logicformat.h and scene/logiccontainer write a scenery's events, their targets and sibling chains as flat, deterministic arrays with a schema hash that refuses a mismatched build. simulation/logiccook cooks it from the loaded state whenever it is missing, stale or unreadable, reads it straight back to check it, and resolves each target to a position in the registry its event kind searches. -cooklogic forces a cook, -verifylogic only checks. Nothing reads it at load yet. Prefabs. scene/prefab describes a signal by the aspects it can show - lights, speeds, animation channels, a sound - instead of pasting an .inc per variant, and expands an instance to ordinary scenery text. Tried on a light signal and a shape signal. Screenshots without a person present: -shot x,y,z[,yaw[,pitch]], -shotsettle, -shotstay.
1359 lines
39 KiB
C++
1359 lines
39 KiB
C++
module;
|
|
#include <cstddef>
|
|
#include <deque>
|
|
#include <chrono>
|
|
#include <limits>
|
|
#include <mutex>
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
#include <array>
|
|
#include <GLFW/glfw3.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include "utilities/Globals_macros.h"
|
|
#include "utilities/translation_macros.h"
|
|
#include "vehicle/DynObj_macros.h"
|
|
|
|
module eu07.application.drivermode;
|
|
import eu07.application.shotrunner;
|
|
import eu07.console; // Console:: is used under _WIN32
|
|
import eu07.application.uitranscripts;
|
|
import eu07.application.uilayer;
|
|
import eu07.audio.sound;
|
|
import eu07.utilities.utilities;
|
|
import eu07.application.driveruilayer;
|
|
import eu07.utilities.globals;
|
|
import eu07.application.application;
|
|
import eu07.utilities.translation;
|
|
import eu07.simulation.simulation;
|
|
import eu07.simulation.simulationtime;
|
|
import eu07.simulation.simulationenvironment;
|
|
import eu07.simcore;
|
|
import eu07.rendering.lightarray;
|
|
import eu07.rendering.particles;
|
|
import eu07.vehicle.train;
|
|
import eu07.input.messaging;
|
|
import eu07.utilities.timer;
|
|
import eu07.rendering.renderer;
|
|
import eu07.utilities.logs;
|
|
/*
|
|
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/.
|
|
*/
|
|
|
|
|
|
/*
|
|
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_input>();
|
|
uart->init();
|
|
}
|
|
#endif
|
|
#ifdef WITH_ZMQ
|
|
if (!Global.zmq_address.empty())
|
|
{
|
|
zmq = std::make_unique<zmq_input>();
|
|
}
|
|
#endif
|
|
if (Global.motiontelemetry_conf.enable)
|
|
telemetry = std::make_unique<motiontelemetry>();
|
|
|
|
#ifdef _WIN32
|
|
Console::On(); // włączenie konsoli
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string driver_mode::drivermode_input::binding_hints(std::pair<user_command, user_command> 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<user_command, std::pair<user_command, user_command>> 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<user_command, user_command> 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<driver_ui>();
|
|
}
|
|
|
|
// 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();
|
|
|
|
// unattended screenshot sequence, when one was asked for on the command line
|
|
if (true == Shots.active())
|
|
{
|
|
switch (Shots.update(Timer::GetDeltaTime()))
|
|
{
|
|
case shot_runner::action::place:
|
|
{
|
|
Camera.Init(Shots.current().position, Shots.current().angle, nullptr);
|
|
Global.pCamera = Camera;
|
|
FreeFlyModeFlag = true;
|
|
break;
|
|
}
|
|
case shot_runner::action::capture:
|
|
{
|
|
// where the lens actually is, which need not be where it was put: anything
|
|
// that moves the camera between placing and capturing would otherwise make
|
|
// the sequence quietly unrepeatable
|
|
WriteLog(
|
|
"Shot: camera at " + to_string(Camera.Pos)
|
|
+ " looking " + to_string(glm::dvec3{Camera.Angle} * 180.0 / M_PI)
|
|
+ (FreeFlyModeFlag ? " (free)" : " (attached)"));
|
|
Application.queue_screenshot();
|
|
break;
|
|
}
|
|
case shot_runner::action::finish:
|
|
{
|
|
Application.queue_quit(false);
|
|
break;
|
|
}
|
|
case shot_runner::action::none:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (deltatime != 0.0 || false == simulation::is_ready)
|
|
{
|
|
// 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<int>( 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<std::uintptr_t>(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.shiftState ? true : Global.ctrlState ? false : !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 && glm::length2(controlled->GetPosition() - Camera.Pos) < sq(1500)) // length2 is better than length for comparing because it does not require sqrt function
|
|
{
|
|
// gdy bliżej niż 1.5km
|
|
Camera.LookAt = controlled->GetPosition() + 0.4 * controlled->VectorUp() * controlled->MoverParameters->Dim.H;
|
|
}
|
|
else
|
|
{
|
|
TDynamicObject *d = std::get<TDynamicObject *>(simulation::Region->find_vehicle(Camera.Pos, 300, false, false));
|
|
if (!d)
|
|
d = std::get<TDynamicObject *>(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 (sq(10.0) * glm::length2(d->GetPosition() - Camera.Pos) > glm::length2(pDynamicNearest->GetPosition() - Camera.Pos)) // length2 is better than length for comparing because it does not require sqrt function
|
|
{
|
|
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 && simulation::Train)
|
|
{
|
|
// 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<float>(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<float>(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.0 : 1.0) * controlled->VectorLeft() * (double)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() * (double)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;
|
|
}
|
|
|
|
float const shakescale{FreeFlyModeFlag ? 5.0f : 1.0f};
|
|
auto shakencamerapos{Camera.m_owneroffset +
|
|
shakescale * glm::vec3(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 ? glm::dvec3(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 * (double)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(glm::degrees(Global.FreeCameraInitAngle[i].x)) + " " + std::to_string(glm::degrees(Global.FreeCameraInitAngle[i].y)) + " " +
|
|
std::to_string(glm::degrees(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<TDynamicObject *>(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() * (double)cab;
|
|
|
|
if (true == Near)
|
|
{
|
|
|
|
Camera.Pos = glm::dvec3(Camera.Pos.x, vehicle->GetPosition().y, Camera.Pos.z) + left * vehicle->GetWidth() + glm::dvec3(1.25 * left.x, 1.6, 1.25 * left.z);
|
|
}
|
|
else
|
|
{
|
|
|
|
Camera.Pos = vehicle->GetPosition() + vehicle->VectorFront() * (double)vehicle->MoverParameters->CabOccupied * 50.0 + glm::dvec3(-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<int>(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 * (double)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;
|
|
}
|