mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-03-22 15:05:03 +01:00
Reorganize source files into logical subdirectories
Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
This commit is contained in:
515
simulation/simulation.cpp
Normal file
515
simulation/simulation.cpp
Normal file
@@ -0,0 +1,515 @@
|
||||
/*
|
||||
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 "simulation.h"
|
||||
#include "simulationtime.h"
|
||||
#include "simulationenvironment.h"
|
||||
|
||||
#include "Globals.h"
|
||||
#include "Event.h"
|
||||
#include "MemCell.h"
|
||||
#include "Track.h"
|
||||
#include "Traction.h"
|
||||
#include "TractionPower.h"
|
||||
#include "sound.h"
|
||||
#include "AnimModel.h"
|
||||
#include "DynObj.h"
|
||||
#include "lightarray.h"
|
||||
#include "particles.h"
|
||||
#include "scene.h"
|
||||
#include "Train.h"
|
||||
#include "application.h"
|
||||
#include "Logs.h"
|
||||
#include "Driver.h"
|
||||
|
||||
namespace simulation {
|
||||
|
||||
state_manager State;
|
||||
event_manager Events;
|
||||
memory_table Memory;
|
||||
path_table Paths;
|
||||
traction_table Traction;
|
||||
powergridsource_table Powergrid;
|
||||
instance_table Instances;
|
||||
vehicle_table Vehicles;
|
||||
train_table Trains;
|
||||
light_array Lights;
|
||||
particle_manager Particles;
|
||||
|
||||
#ifdef WITH_LUA
|
||||
lua Lua;
|
||||
#endif
|
||||
|
||||
scene::basic_region *Region { nullptr };
|
||||
TTrain *Train { nullptr };
|
||||
|
||||
uint16_t prev_train_id { 0 };
|
||||
bool is_ready { false };
|
||||
|
||||
std::shared_ptr<deserializer_state>
|
||||
state_manager::deserialize_begin(std::string const &Scenariofile) {
|
||||
|
||||
return m_serializer.deserialize_begin( Scenariofile );
|
||||
}
|
||||
|
||||
bool
|
||||
state_manager::deserialize_continue(std::shared_ptr<deserializer_state> state) {
|
||||
|
||||
return m_serializer.deserialize_continue(state);
|
||||
}
|
||||
|
||||
// stores class data in specified file, in legacy (text) format
|
||||
void
|
||||
state_manager::export_as_text( std::string const &Scenariofile ) const {
|
||||
|
||||
return m_serializer.export_as_text( Scenariofile );
|
||||
}
|
||||
|
||||
void
|
||||
state_manager::init_scripting_interface() {
|
||||
|
||||
// create scenario data memory cells
|
||||
{
|
||||
auto *memorycell = new TMemCell( {
|
||||
0, -1,
|
||||
"__simulation.weather",
|
||||
"memcell" } );
|
||||
simulation::Memory.insert( memorycell );
|
||||
simulation::Region->insert( memorycell );
|
||||
}
|
||||
{
|
||||
auto *memorycell = new TMemCell( {
|
||||
0, -1,
|
||||
"__simulation.time",
|
||||
"memcell" } );
|
||||
simulation::Memory.insert( memorycell );
|
||||
simulation::Region->insert( memorycell );
|
||||
}
|
||||
{
|
||||
auto *memorycell = new TMemCell( {
|
||||
0, -1,
|
||||
"__simulation.date",
|
||||
"memcell" } );
|
||||
simulation::Memory.insert( memorycell );
|
||||
simulation::Region->insert( memorycell );
|
||||
}
|
||||
}
|
||||
|
||||
// legacy method, calculates changes in simulation state over specified time
|
||||
void
|
||||
state_manager::update( double const Deltatime, int Iterationcount ) {
|
||||
// aktualizacja animacji krokiem FPS: dt=krok czasu [s], dt*iter=czas od ostatnich przeliczeń
|
||||
if (Deltatime == 0.0) { return; }
|
||||
|
||||
auto const totaltime { Deltatime * Iterationcount };
|
||||
// NOTE: we perform animations first, as they can determine factors like contact with powergrid
|
||||
TAnimModel::AnimUpdate( totaltime ); // wykonanie zakolejkowanych animacji
|
||||
|
||||
simulation::Powergrid.update( totaltime );
|
||||
simulation::Vehicles.update( Deltatime, Iterationcount );
|
||||
}
|
||||
|
||||
void
|
||||
state_manager::update_clocks() {
|
||||
|
||||
// Ra 2014-07: przeliczenie kąta czasu (do animacji zależnych od czasu)
|
||||
auto const &time = simulation::Time.data();
|
||||
Global.fTimeAngleDeg = time.wHour * 15.0 + time.wMinute * 0.25 + ( ( time.wSecond + 0.001 * time.wMilliseconds ) / 240.0 );
|
||||
Global.fClockAngleDeg[ 0 ] = 36.0 * ( time.wSecond % 10 ); // jednostki sekund
|
||||
Global.fClockAngleDeg[ 1 ] = 36.0 * ( time.wSecond / 10 ); // dziesiątki sekund
|
||||
Global.fClockAngleDeg[ 2 ] = 36.0 * ( time.wMinute % 10 ); // jednostki minut
|
||||
Global.fClockAngleDeg[ 3 ] = 36.0 * ( time.wMinute / 10 ); // dziesiątki minut
|
||||
Global.fClockAngleDeg[ 4 ] = 36.0 * ( time.wHour % 10 ); // jednostki godzin
|
||||
Global.fClockAngleDeg[ 5 ] = 36.0 * ( time.wHour / 10 ); // dziesiątki godzin
|
||||
}
|
||||
|
||||
void
|
||||
state_manager::update_scripting_interface() {
|
||||
|
||||
auto *weather{ Memory.find( "__simulation.weather" ) };
|
||||
auto *time{ Memory.find( "__simulation.time" ) };
|
||||
auto *date{ Memory.find( "__simulation.date" ) };
|
||||
|
||||
if( simulation::is_ready ) {
|
||||
// potentially adjust weather
|
||||
if( weather->Value1() != m_scriptinginterface.weather->Value1() ) {
|
||||
Global.Overcast = clamp<float>( weather->Value1(), 0, 2 );
|
||||
simulation::Environment.compute_weather();
|
||||
}
|
||||
if( weather->Value2() != m_scriptinginterface.weather->Value2() ) {
|
||||
Global.fFogEnd = clamp<float>( weather->Value2(), 10, 25000 );
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_scriptinginterface.weather = std::make_shared<TMemCell>( scene::node_data() );
|
||||
m_scriptinginterface.date = std::make_shared<TMemCell>( scene::node_data() );
|
||||
m_scriptinginterface.time = std::make_shared<TMemCell>( scene::node_data() );
|
||||
}
|
||||
|
||||
// update scripting interface
|
||||
weather->UpdateValues(
|
||||
Global.Weather,
|
||||
Global.Overcast,
|
||||
Global.fFogEnd,
|
||||
basic_event::flags::text | basic_event::flags::value1 | basic_event::flags::value2 );
|
||||
|
||||
time->UpdateValues(
|
||||
Global.Period,
|
||||
Time.data().wHour,
|
||||
Time.data().wMinute,
|
||||
basic_event::flags::text | basic_event::flags::value1 | basic_event::flags::value2 );
|
||||
|
||||
date->UpdateValues(
|
||||
Global.Season,
|
||||
Time.year_day(),
|
||||
0,
|
||||
basic_event::flags::text | basic_event::flags::value1 );
|
||||
|
||||
// cache cell state to detect potential script-issued changes on next cycle
|
||||
*m_scriptinginterface.weather = *weather;
|
||||
*m_scriptinginterface.time = *time;
|
||||
*m_scriptinginterface.date = *date;
|
||||
}
|
||||
|
||||
void state_manager::process_commands() {
|
||||
command_data commanddata;
|
||||
while( Commands.pop( commanddata, (uint32_t)command_target::simulation )) {
|
||||
if (commanddata.command == user_command::consistreleaser) {
|
||||
TDynamicObject *found_vehicle = simulation::Vehicles.find(commanddata.payload);
|
||||
TDynamicObject *vehicle = found_vehicle;
|
||||
|
||||
while (vehicle) {
|
||||
vehicle->MoverParameters->Hamulec->Releaser(commanddata.action != GLFW_RELEASE ? 1 : 0);
|
||||
vehicle = vehicle->Next();
|
||||
}
|
||||
|
||||
vehicle = found_vehicle;
|
||||
while (vehicle) {
|
||||
vehicle->MoverParameters->Hamulec->Releaser(commanddata.action != GLFW_RELEASE ? 1 : 0);
|
||||
vehicle = vehicle->Prev();
|
||||
}
|
||||
}
|
||||
|
||||
if (commanddata.action == GLFW_RELEASE) {
|
||||
if (commanddata.command == user_command::debugtoggle)
|
||||
DebugModeFlag = !DebugModeFlag;
|
||||
|
||||
if (commanddata.command == user_command::pausetoggle) {
|
||||
if( Global.iPause & 1 ) {
|
||||
// jeśli pauza startowa
|
||||
// odpauzowanie, gdy po wczytaniu miało nie startować
|
||||
Global.iPause ^= 1;
|
||||
}
|
||||
else {
|
||||
Global.iPause ^= 2; // zmiana stanu zapauzowania
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::focuspauseset) {
|
||||
if( commanddata.param1 == 1.0 )
|
||||
Global.iPause &= ~4; // odpauzowanie, gdy jest na pierwszym planie
|
||||
else
|
||||
Global.iPause |= 4; // włączenie pauzy, gdy nieaktywy
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::entervehicle) {
|
||||
// przesiadka do innego pojazdu
|
||||
if( commanddata.payload == "ghostview" ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!commanddata.freefly)
|
||||
// only available in free fly mode
|
||||
continue;
|
||||
|
||||
// NOTE: because malformed scenario can have vehicle name duplicates we first try to locate vehicle in world, with name search as fallback
|
||||
TDynamicObject *targetvehicle = std::get<TDynamicObject *>( simulation::Region->find_vehicle( commanddata.location, 50, false, false ) );
|
||||
if( ( targetvehicle == nullptr ) || ( targetvehicle->name() != commanddata.payload ) ) {
|
||||
targetvehicle = simulation::Vehicles.find( commanddata.payload );
|
||||
}
|
||||
|
||||
if (!targetvehicle)
|
||||
continue;
|
||||
|
||||
auto *senderlocaltrain { simulation::Trains.find_id( static_cast<std::uint16_t>( commanddata.param2 ) ) };
|
||||
if( senderlocaltrain ) {
|
||||
auto *currentvehicle { senderlocaltrain->Dynamic() };
|
||||
auto const samevehicle { currentvehicle == targetvehicle };
|
||||
|
||||
if( samevehicle ) {
|
||||
// we already control desired vehicle so don't overcomplicate things
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const sameconsist{
|
||||
( targetvehicle->ctOwner == currentvehicle->Mechanik )
|
||||
|| ( targetvehicle->ctOwner == currentvehicle->ctOwner ) };
|
||||
auto const isincharge{ currentvehicle->Mechanik->primary() };
|
||||
auto const aidriveractive{ currentvehicle->Mechanik->AIControllFlag };
|
||||
// TODO: support for primary mode request passed as commanddata.param1
|
||||
if( !sameconsist && isincharge ) {
|
||||
// oddajemy dotychczasowy AI
|
||||
if (currentvehicle->Mechanik != nullptr)
|
||||
currentvehicle->Mechanik->TakeControl( true );
|
||||
}
|
||||
|
||||
if( sameconsist && !aidriveractive ) {
|
||||
// since we're consist owner we can simply move to the destination vehicle
|
||||
senderlocaltrain->MoveToVehicle( targetvehicle );
|
||||
if (senderlocaltrain->Dynamic()->Mechanik != nullptr)
|
||||
senderlocaltrain->Dynamic()->Mechanik->TakeControl( false, true );
|
||||
}
|
||||
}
|
||||
|
||||
auto *train { simulation::Trains.find( targetvehicle->name() ) };
|
||||
|
||||
if (train)
|
||||
continue;
|
||||
|
||||
train = new TTrain();
|
||||
if (train->Init(targetvehicle)) {
|
||||
simulation::Trains.insert(train);
|
||||
}
|
||||
else {
|
||||
delete train;
|
||||
train = nullptr;
|
||||
if( targetvehicle->name() == Global.local_start_vehicle ) {
|
||||
ErrorLog( "Failed to initialize player train, \"" + Global.local_start_vehicle + "\"" );
|
||||
Global.local_start_vehicle = "ghostview";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::queueevent) {
|
||||
std::istringstream ss(commanddata.payload);
|
||||
|
||||
std::string event_name;
|
||||
std::string vehicle_name;
|
||||
std::getline(ss, event_name, '%');
|
||||
std::getline(ss, vehicle_name, '%');
|
||||
|
||||
basic_event *ev = Events.FindEvent(event_name);
|
||||
TDynamicObject *vehicle = nullptr;
|
||||
if (!vehicle_name.empty())
|
||||
vehicle = simulation::Vehicles.find(vehicle_name);
|
||||
|
||||
if (ev)
|
||||
Events.AddToQuery(ev, vehicle);
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::setlight) {
|
||||
int light = std::round(commanddata.param1);
|
||||
float state = commanddata.param2;
|
||||
TAnimModel *model = simulation::Instances.find(commanddata.payload);
|
||||
if (model)
|
||||
model->LightSet(light, state);
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::setdatetime) {
|
||||
int yearday = std::round(commanddata.param1);
|
||||
int minute = std::round(commanddata.param2);
|
||||
simulation::Time.set_time(yearday, minute);
|
||||
|
||||
auto const weather { Global.Weather };
|
||||
simulation::Environment.compute_season(yearday);
|
||||
if( weather != Global.Weather ) {
|
||||
// HACK: force re-calculation of precipitation
|
||||
Global.Overcast = clamp( Global.Overcast - 0.0001f, 0.0f, 2.0f );
|
||||
}
|
||||
|
||||
simulation::Environment.update_moon();
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::setweather) {
|
||||
Global.fFogEnd = commanddata.param1;
|
||||
Global.Overcast = commanddata.param2;
|
||||
|
||||
simulation::Environment.compute_weather();
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::settemperature) {
|
||||
Global.AirTemperature = commanddata.param1;
|
||||
Global.Overcast = commanddata.param2;
|
||||
simulation::Environment.compute_weather();
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::insertmodel) {
|
||||
std::istringstream ss(commanddata.payload);
|
||||
|
||||
std::string name;
|
||||
std::string data;
|
||||
std::getline(ss, name, ':');
|
||||
std::getline(ss, data, ':');
|
||||
|
||||
TAnimModel *model = simulation::State.create_model(data, name, commanddata.location);
|
||||
simulation::State.create_eventlauncher("node -1 0 launcher eventlauncher 0 0 0 0.8 none -10000.0 obstacle_collision traintriggered end", name + "_snd", commanddata.location);
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::deletemodel) {
|
||||
simulation::State.delete_model(simulation::Instances.find(commanddata.payload));
|
||||
simulation::State.delete_eventlauncher(simulation::Events.FindEventlauncher(commanddata.payload + "_snd"));
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::globalradiostop) {
|
||||
simulation::Region->RadioStop( commanddata.location );
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::resetconsist) {
|
||||
TDynamicObject *found_vehicle = simulation::Vehicles.find(commanddata.payload);
|
||||
TDynamicObject *vehicle = found_vehicle;
|
||||
|
||||
while (vehicle) {
|
||||
if (vehicle->Next())
|
||||
vehicle = vehicle->Next();
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
while (vehicle) {
|
||||
vehicle->MoverParameters->DamageFlag = 0;
|
||||
vehicle->MoverParameters->EngDmgFlag = 0;
|
||||
vehicle->MoverParameters->V = 0.000001; // HACK: force vehicle position re-calculation
|
||||
vehicle->MoverParameters->DistCounter = 0.0;
|
||||
vehicle->MoverParameters->WheelFlat = 0.0;
|
||||
vehicle->MoverParameters->AlarmChainFlag = false;
|
||||
vehicle->MoverParameters->OffsetTrackH = 0.0;
|
||||
vehicle->MoverParameters->OffsetTrackV = 0.0;
|
||||
|
||||
// pantographs
|
||||
for( auto idx = 0; idx < vehicle->iAnimType[ ANIM_PANTS ]; ++idx ) {
|
||||
auto &pantograph { *( vehicle->pants[ idx ].fParamPants ) };
|
||||
if( pantograph.PantWys >= 0.0 ) // negative value means pantograph is broken
|
||||
continue;
|
||||
pantograph.fAngleL = pantograph.fAngleL0;
|
||||
pantograph.fAngleU = pantograph.fAngleU0;
|
||||
pantograph.PantWys =
|
||||
pantograph.fLenL1 * std::sin( pantograph.fAngleL )
|
||||
+ pantograph.fLenU1 * std::sin( pantograph.fAngleU )
|
||||
+ pantograph.fHeight;
|
||||
vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo;
|
||||
}
|
||||
|
||||
vehicle = vehicle->Prev();
|
||||
}
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::fillcompressor) {
|
||||
TDynamicObject *vehicle = simulation::Vehicles.find(commanddata.payload);
|
||||
vehicle->MoverParameters->CompressedVolume = 8.0f * vehicle->MoverParameters->VeselVolume;
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::dynamicmove) {
|
||||
TDynamicObject *vehicle = simulation::Vehicles.find(commanddata.payload);
|
||||
if (vehicle)
|
||||
vehicle->move_set(commanddata.param1);
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::consistteleport) {
|
||||
std::istringstream ss(commanddata.payload);
|
||||
|
||||
std::string track_name;
|
||||
std::string vehicle_name;
|
||||
std::getline(ss, vehicle_name, '%');
|
||||
std::getline(ss, track_name, '%');
|
||||
|
||||
TTrack *track = simulation::Paths.find(track_name);
|
||||
TDynamicObject *vehicle = simulation::Vehicles.find(vehicle_name);
|
||||
|
||||
while (vehicle) {
|
||||
if (vehicle->Next())
|
||||
vehicle = vehicle->Next();
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
double offset = 0.0;
|
||||
|
||||
while (vehicle) {
|
||||
offset += vehicle->MoverParameters->Dim.L;
|
||||
vehicle->place_on_track(track, offset, false);
|
||||
vehicle = vehicle->Prev();
|
||||
}
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::spawntrainset) {
|
||||
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::pullalarmchain) {
|
||||
TDynamicObject *vehicle = simulation::Vehicles.find(commanddata.payload);
|
||||
if (vehicle)
|
||||
vehicle->MoverParameters->AlarmChainSwitch(true);
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::sendaicommand) {
|
||||
std::istringstream ss(commanddata.payload);
|
||||
|
||||
std::string vehicle_name;
|
||||
std::string command;
|
||||
std::getline(ss, vehicle_name, '%');
|
||||
std::getline(ss, command, '%');
|
||||
|
||||
TDynamicObject *vehicle = simulation::Vehicles.find(vehicle_name);
|
||||
glm::dvec3 location = commanddata.location;
|
||||
if (vehicle && vehicle->Mechanik)
|
||||
vehicle->Mechanik->PutCommand(command, commanddata.param1, commanddata.param2, &location);
|
||||
}
|
||||
|
||||
if (commanddata.command == user_command::quitsimulation) {
|
||||
// TBD: allow clients to go into offline mode?
|
||||
Application.queue_quit(true);
|
||||
}
|
||||
|
||||
if (DebugModeFlag) {
|
||||
if (commanddata.command == user_command::timejump) {
|
||||
Time.update(commanddata.param1);
|
||||
}
|
||||
else if (commanddata.command == user_command::timejumplarge) {
|
||||
Time.update(20.0 * 60.0);
|
||||
}
|
||||
else if (commanddata.command == user_command::timejumpsmall) {
|
||||
Time.update(5.0 * 60.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TAnimModel * state_manager::create_model(const std::string &src, const std::string &name, const glm::dvec3 &position) {
|
||||
return m_serializer.create_model(src, name, position);
|
||||
}
|
||||
|
||||
TEventLauncher * state_manager::create_eventlauncher(const std::string &src, const std::string &name, const glm::dvec3 &position) {
|
||||
return m_serializer.create_eventlauncher(src, name, position);
|
||||
}
|
||||
|
||||
void state_manager::delete_model(TAnimModel *model) {
|
||||
Region->erase(model);
|
||||
Instances.purge(model);
|
||||
}
|
||||
|
||||
void state_manager::delete_eventlauncher(TEventLauncher *launcher) {
|
||||
launcher->dRadius = 0.0f; // disable it
|
||||
}
|
||||
|
||||
// passes specified sound to all vehicles within range as a radio message broadcasted on specified channel
|
||||
void
|
||||
radio_message( sound_source *Message, int const Channel ) {
|
||||
|
||||
if( Train != nullptr ) {
|
||||
Train->radio_message( Message, Channel );
|
||||
}
|
||||
}
|
||||
|
||||
} // simulation
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
99
simulation/simulation.h
Normal file
99
simulation/simulation.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "simulationstateserializer.h"
|
||||
#include "Classes.h"
|
||||
#include "Event.h"
|
||||
#include "Train.h"
|
||||
#include "particles.h"
|
||||
|
||||
#ifdef WITH_LUA
|
||||
#include "lua.h"
|
||||
#endif
|
||||
|
||||
namespace simulation {
|
||||
|
||||
class state_manager {
|
||||
|
||||
public:
|
||||
// methods
|
||||
void
|
||||
init_scripting_interface();
|
||||
// legacy method, calculates changes in simulation state over specified time
|
||||
void
|
||||
update( double Deltatime, int Iterationcount );
|
||||
void
|
||||
update_clocks();
|
||||
void
|
||||
update_scripting_interface();
|
||||
// process input commands
|
||||
void
|
||||
process_commands();
|
||||
// create model from node string
|
||||
TAnimModel *
|
||||
create_model(const std::string &src, const std::string &name, const glm::dvec3 &position);
|
||||
// create eventlauncher from node string
|
||||
TEventLauncher *
|
||||
create_eventlauncher(const std::string &src, const std::string &name, const glm::dvec3 &position);
|
||||
// delete TAnimModel instance
|
||||
void
|
||||
delete_model(TAnimModel *model);
|
||||
// delete TEventLauncher instance
|
||||
void
|
||||
delete_eventlauncher(TEventLauncher *launcher);
|
||||
// starts deserialization from specified file, returns context pointer on success, throws otherwise
|
||||
std::shared_ptr<deserializer_state>
|
||||
deserialize_begin(std::string const &Scenariofile);
|
||||
// continues deserialization for given context, amount limited by time, returns true if needs to be called again
|
||||
bool
|
||||
deserialize_continue(std::shared_ptr<deserializer_state> state);
|
||||
// stores class data in specified file, in legacy (text) format
|
||||
void
|
||||
export_as_text( std::string const &Scenariofile ) const;
|
||||
|
||||
private:
|
||||
// members
|
||||
state_serializer m_serializer;
|
||||
struct {
|
||||
std::shared_ptr<TMemCell> weather, time, date;
|
||||
} m_scriptinginterface;
|
||||
};
|
||||
|
||||
// passes specified sound to all vehicles within range as a radio message broadcasted on specified channel
|
||||
void radio_message( sound_source *Message, int const Channel );
|
||||
|
||||
extern state_manager State;
|
||||
extern event_manager Events;
|
||||
extern memory_table Memory;
|
||||
extern path_table Paths;
|
||||
extern traction_table Traction;
|
||||
extern powergridsource_table Powergrid;
|
||||
extern instance_table Instances;
|
||||
extern vehicle_table Vehicles;
|
||||
extern train_table Trains;
|
||||
extern light_array Lights;
|
||||
extern sound_table Sounds;
|
||||
extern particle_manager Particles;
|
||||
#ifdef WITH_LUA
|
||||
extern lua Lua;
|
||||
#endif
|
||||
|
||||
extern scene::basic_region *Region;
|
||||
extern TTrain *Train;
|
||||
|
||||
extern uint16_t prev_train_id;
|
||||
extern bool is_ready;
|
||||
|
||||
struct deserializer_state;
|
||||
|
||||
} // simulation
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
277
simulation/simulationenvironment.cpp
Normal file
277
simulation/simulationenvironment.cpp
Normal file
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
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 "simulationenvironment.h"
|
||||
|
||||
#include "simulationsounds.h"
|
||||
#include "Globals.h"
|
||||
#include "Timer.h"
|
||||
|
||||
namespace simulation {
|
||||
|
||||
world_environment Environment;
|
||||
|
||||
} // simulation
|
||||
|
||||
void
|
||||
world_environment::on_daylight_change() {
|
||||
|
||||
if( Global.FakeLight ) {
|
||||
// for fake daylight enter fixed hour
|
||||
time( 10, 30, 0 );
|
||||
}
|
||||
else {
|
||||
// local clock based calculation
|
||||
time();
|
||||
}
|
||||
}
|
||||
|
||||
// calculates current season of the year based on set simulation date
|
||||
void
|
||||
world_environment::compute_season( int const Yearday ) {
|
||||
|
||||
using dayseasonpair = std::pair<int, std::string>;
|
||||
|
||||
std::vector<dayseasonpair> seasonsequence {
|
||||
{ 65, "winter:" },
|
||||
{ 158, "spring:" },
|
||||
{ 252, "summer:" },
|
||||
{ 341, "autumn:" },
|
||||
{ 366, "winter:" } };
|
||||
auto const lookup =
|
||||
std::lower_bound(
|
||||
std::begin( seasonsequence ), std::end( seasonsequence ),
|
||||
clamp( Yearday, 1, seasonsequence.back().first ),
|
||||
[]( dayseasonpair const &Left, const int Right ) {
|
||||
return Left.first < Right; } );
|
||||
|
||||
Global.Season = lookup->second;
|
||||
// season can affect the weather so if it changes, re-calculate weather as well
|
||||
compute_weather();
|
||||
}
|
||||
|
||||
// calculates current weather
|
||||
void
|
||||
world_environment::compute_weather() {
|
||||
|
||||
Global.Weather = (
|
||||
Global.Overcast <= 0.10 ? "clear:" :
|
||||
Global.Overcast <= 0.50 ? "scattered:" :
|
||||
Global.Overcast <= 0.90 ? "broken:" :
|
||||
Global.Overcast <= 1.00 ? "overcast:" :
|
||||
|
||||
(Global.AirTemperature > 1 ? "rain:" :
|
||||
"snow:" ) );
|
||||
|
||||
Global.fTurbidity = (
|
||||
Global.Overcast <= 0.10 ? 3 :
|
||||
Global.Overcast <= 0.20 ? 4 :
|
||||
Global.Overcast <= 0.30 ? 5 :
|
||||
Global.Overcast <= 0.40 ? 5 :
|
||||
Global.Overcast <= 0.50 ? 5 :
|
||||
Global.Overcast <= 0.60 ? 5 :
|
||||
Global.Overcast <= 0.70 ? 6 :
|
||||
Global.Overcast <= 0.80 ? 7 :
|
||||
Global.Overcast <= 0.90 ? 8 :
|
||||
Global.Overcast > 0.90 ? 9 :
|
||||
9
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
world_environment::init() {
|
||||
|
||||
m_sun.init();
|
||||
m_moon.init();
|
||||
m_stars.init();
|
||||
m_clouds.Init();
|
||||
m_precipitation.init();
|
||||
{
|
||||
auto const rainsoundoverride { simulation::Sound_overrides.find( "weather.rainsound:" ) };
|
||||
m_rainsound.deserialize(
|
||||
( rainsoundoverride != simulation::Sound_overrides.end() ?
|
||||
rainsoundoverride->second :
|
||||
"rain-sound-loop" ),
|
||||
sound_type::single );
|
||||
}
|
||||
m_wind = basic_wind{
|
||||
static_cast<float>( Random( 0, 360 ) ),
|
||||
static_cast<float>( Random( -5, 5 ) ),
|
||||
static_cast<float>( Random( -2, 4 ) ),
|
||||
static_cast<float>( Random( -1, 1 ) ),
|
||||
static_cast<float>( Random( 5, 20 ) ),
|
||||
{} };
|
||||
}
|
||||
|
||||
void
|
||||
world_environment::update() {
|
||||
// move celestial bodies...
|
||||
m_sun.update();
|
||||
m_moon.update();
|
||||
// ...determine source of key light and adjust global state accordingly...
|
||||
// diffuse (sun) intensity goes down after twilight, and reaches minimum 18 degrees below horizon
|
||||
float twilightfactor = clamp( -m_sun.getAngle(), 0.0f, 18.0f ) / 18.0f;
|
||||
// NOTE: sun light receives extra padding to prevent moon from kicking in too soon
|
||||
auto const sunlightlevel = m_sun.getIntensity() + 0.05f * ( 1.f - twilightfactor );
|
||||
auto const moonlightlevel = m_moon.getIntensity() * 0.65f; // scaled down by arbitrary factor, it's pretty bright otherwise
|
||||
|
||||
// ...update skydome to match the current sun position as well...
|
||||
// twilight factor can be reset later down, so we do it here while it's still reflecting state of the sun
|
||||
// turbidity varies from 2-3 during the day based on overcast, 3-4 after sunset to deal with sunlight bleeding too much into the sky from below horizon
|
||||
m_skydome.SetTurbidity(
|
||||
2.25f
|
||||
+ clamp( Global.Overcast, 0.f, 1.f )
|
||||
+ interpolate( 0.f, 1.f, clamp( twilightfactor * 1.5f, 0.f, 1.f ) ) );
|
||||
m_skydome.SetOvercastFactor( Global.Overcast );
|
||||
m_skydome.Update( m_sun.getDirection() );
|
||||
|
||||
float keylightintensity;
|
||||
glm::vec3 keylightcolor;
|
||||
Global.SunAngle = m_sun.getAngle();
|
||||
if ((moonlightlevel > sunlightlevel) && (Global.SunAngle <(-20)))
|
||||
{
|
||||
// rare situations when the moon is brighter than the sun, typically at night
|
||||
Global.SunAngle = m_moon.getAngle();
|
||||
Global.DayLight.position = m_moon.getDirection();
|
||||
Global.DayLight.direction = -1.0f * m_moon.getDirection();
|
||||
keylightintensity = moonlightlevel;
|
||||
m_lightintensity = moonlightlevel;
|
||||
// if the moon is up, it overrides the twilight
|
||||
twilightfactor = 0.0f;
|
||||
keylightcolor = glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 202.0f / 255.0f );
|
||||
}
|
||||
else {
|
||||
// regular situation with sun as the key light
|
||||
Global.SunAngle = m_sun.getAngle();
|
||||
Global.DayLight.position = m_sun.getDirection();
|
||||
Global.DayLight.direction = -1.0f * m_sun.getDirection();
|
||||
keylightintensity = sunlightlevel;
|
||||
m_lightintensity = 1.0f;
|
||||
// include 'golden hour' effect in twilight lighting
|
||||
float const duskfactor = 1.25f - clamp( Global.SunAngle, 0.0f, 18.0f ) / 18.0f;
|
||||
keylightcolor = interpolate(
|
||||
glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ),
|
||||
glm::vec3( 235.0f / 255.0f, 120.0f / 255.0f, 36.0f / 255.0f ),
|
||||
duskfactor );
|
||||
}
|
||||
// ...retrieve current sky colour and brightness...
|
||||
auto const skydomecolour = m_skydome.GetAverageColor();
|
||||
auto const skydomehsv = colors::RGBtoHSV( skydomecolour );
|
||||
// sun strength is reduced by overcast level
|
||||
keylightintensity *= ( 1.0f - std::min( 1.f, Global.Overcast ) * 0.65f );
|
||||
|
||||
// intensity combines intensity of the sun and the light reflected by the sky dome
|
||||
// it'd be more technically correct to have just the intensity of the sun here,
|
||||
// but whether it'd _look_ better is something to be tested
|
||||
auto const intensity = std::min( 1.15f * ( 0.05f + keylightintensity + skydomehsv.z ), 1.25f );
|
||||
// the impact of sun component is reduced proportionally to overcast level, as overcast increases role of ambient light
|
||||
auto const diffuselevel = interpolate( keylightintensity, intensity * ( 1.0f - twilightfactor ), 1.0f - std::min( 1.f, Global.Overcast ) * 0.75f );
|
||||
// ...update light colours and intensity.
|
||||
keylightcolor = keylightcolor * diffuselevel;
|
||||
Global.DayLight.diffuse = glm::vec4( keylightcolor, Global.DayLight.diffuse.a );
|
||||
Global.DayLight.specular = glm::vec4( keylightcolor * 0.85f, diffuselevel );
|
||||
|
||||
// tonal impact of skydome color is inversely proportional to how high the sun is above the horizon
|
||||
// (this is pure conjecture, aimed more to 'look right' than be accurate)
|
||||
float const ambienttone = clamp( 1.0f - ( Global.SunAngle / 90.0f ), 0.0f, 1.0f );
|
||||
float const ambientintensitynightfactor = 1.f - 0.75f * clamp( -m_sun.getAngle(), 0.0f, 18.0f ) / 18.0f;
|
||||
Global.DayLight.ambient[ 0 ] = interpolate( skydomehsv.z, skydomecolour.r, ambienttone ) * ambientintensitynightfactor;
|
||||
Global.DayLight.ambient[ 1 ] = interpolate( skydomehsv.z, skydomecolour.g, ambienttone ) * ambientintensitynightfactor;
|
||||
Global.DayLight.ambient[ 2 ] = interpolate( skydomehsv.z, skydomecolour.b, ambienttone ) * ambientintensitynightfactor;
|
||||
|
||||
Global.fLuminance = intensity;
|
||||
|
||||
// update the fog. setting it to match the average colour of the sky dome is cheap
|
||||
// but quite effective way to make the distant items blend with background better
|
||||
Global.FogColor = ((m_skydome.GetAverageHorizonColor()) * keylightcolor) *
|
||||
clamp<float>(Global.fLuminance, 0.0f, 1.f);
|
||||
|
||||
|
||||
// weather-related simulation factors
|
||||
Global.FrictionWeatherFactor = (
|
||||
Global.Weather == "rain:" ? 0.85f :
|
||||
Global.Weather == "snow:" ? 0.75f :
|
||||
1.0f );
|
||||
|
||||
Global.Period = (
|
||||
m_sun.getAngle() > -12.0f ?
|
||||
"day:" :
|
||||
"night:" );
|
||||
|
||||
if( ( true == ( FreeFlyModeFlag || Global.CabWindowOpen ) )
|
||||
&& ( Global.Weather == "rain:" ) ) {
|
||||
if( m_rainsound.is_combined() ) {
|
||||
m_rainsound.pitch( Global.Overcast - 1.0 );
|
||||
}
|
||||
m_rainsound
|
||||
.gain( m_rainsound.m_amplitudeoffset + m_rainsound.m_amplitudefactor * 1.f )
|
||||
.play( sound_flags::exclusive | sound_flags::looping );
|
||||
}
|
||||
else {
|
||||
m_rainsound.stop();
|
||||
}
|
||||
|
||||
update_wind();
|
||||
}
|
||||
|
||||
void
|
||||
world_environment::update_precipitation() {
|
||||
|
||||
m_precipitation.update();
|
||||
}
|
||||
|
||||
void
|
||||
world_environment::update_moon() {
|
||||
|
||||
m_moon.update( true );
|
||||
}
|
||||
|
||||
void
|
||||
world_environment::update_wind() {
|
||||
|
||||
auto const timedelta{ static_cast<float>( Timer::GetDeltaTime() ) };
|
||||
|
||||
m_wind.change_time -= timedelta;
|
||||
if( m_wind.change_time < 0 ) {
|
||||
m_wind.change_time = Random( 5, 15 );
|
||||
m_wind.velocity_change = Random( -0.2, 0.2 );
|
||||
if( Random() < 0.05 ) {
|
||||
// changes in wind direction should be less frequent than changes in wind speed
|
||||
// TBD, TODO: configuration-driven direction change frequency
|
||||
m_wind.azimuth_change = Random( -5, 5 );
|
||||
}
|
||||
else {
|
||||
// keep direction change periods short, to avoid too drastic changes in direction
|
||||
m_wind.azimuth_change = 0.0;
|
||||
}
|
||||
}
|
||||
// TBD, TODO: wind configuration
|
||||
m_wind.azimuth = clamp_circular( m_wind.azimuth + m_wind.azimuth_change * timedelta );
|
||||
// HACK: negative part of range allows for some quiet periods, without active wind
|
||||
m_wind.velocity = clamp<float>( m_wind.velocity + m_wind.velocity_change * timedelta, -2, 4 );
|
||||
// convert to force vector
|
||||
auto const polarangle { glm::radians( 90.f ) }; // theta
|
||||
auto const azimuthalangle{ glm::radians( m_wind.azimuth ) }; // phi
|
||||
// convert spherical coordinates to opengl coordinates
|
||||
m_wind.vector =
|
||||
std::max( 0.f, m_wind.velocity )
|
||||
* glm::vec3(
|
||||
std::sin( polarangle ) * std::sin( azimuthalangle ) * -1,
|
||||
std::cos( polarangle ),
|
||||
std::sin( polarangle ) * std::cos( azimuthalangle ) );
|
||||
}
|
||||
|
||||
void
|
||||
world_environment::time( int const Hour, int const Minute, int const Second ) {
|
||||
|
||||
m_sun.setTime( Hour, Minute, Second );
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
100
simulation/simulationenvironment.h
Normal file
100
simulation/simulationenvironment.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sky.h"
|
||||
#include "sun.h"
|
||||
#include "moon.h"
|
||||
#include "stars.h"
|
||||
#include "skydome.h"
|
||||
#include "precipitation.h"
|
||||
#include "sound.h"
|
||||
|
||||
class opengl_renderer;
|
||||
class opengl33_renderer;
|
||||
|
||||
// wrapper for environment elements -- sky, sun, stars, clouds etc
|
||||
class world_environment {
|
||||
|
||||
friend opengl_renderer;
|
||||
friend opengl33_renderer;
|
||||
|
||||
public:
|
||||
// methods
|
||||
void init();
|
||||
void update();
|
||||
void update_precipitation();
|
||||
void update_moon();
|
||||
void time( int const Hour = -1, int const Minute = -1, int const Second = -1 );
|
||||
// switches between static and dynamic daylight calculation
|
||||
void on_daylight_change();
|
||||
// calculates current season of the year based on set simulation date
|
||||
void compute_season( int const Yearday );
|
||||
// calculates current weather
|
||||
void compute_weather();
|
||||
// data access
|
||||
inline auto const &
|
||||
sun() const {
|
||||
return m_sun; }
|
||||
inline auto const &
|
||||
moon() const {
|
||||
return m_moon; }
|
||||
inline auto const &
|
||||
light_intensity() const {
|
||||
return m_lightintensity; }
|
||||
inline auto const &
|
||||
skydome() const {
|
||||
return m_skydome; }
|
||||
inline auto &
|
||||
skydome() {
|
||||
return m_skydome; }
|
||||
inline auto const &
|
||||
precipitation() const {
|
||||
return m_precipitation; }
|
||||
inline auto const &
|
||||
wind() const {
|
||||
return m_wind.vector; }
|
||||
inline auto const &
|
||||
wind_azimuth() const {
|
||||
return m_wind.azimuth; }
|
||||
|
||||
private:
|
||||
// types
|
||||
struct basic_wind {
|
||||
// internal state data
|
||||
float azimuth;
|
||||
float azimuth_change;
|
||||
float velocity;
|
||||
float velocity_change;
|
||||
float change_time;
|
||||
// output
|
||||
glm::vec3 vector;
|
||||
};
|
||||
// methods
|
||||
void update_wind();
|
||||
// members
|
||||
CSkyDome m_skydome;
|
||||
cStars m_stars;
|
||||
cSun m_sun;
|
||||
cMoon m_moon;
|
||||
float m_lightintensity { 1.f };
|
||||
TSky m_clouds;
|
||||
basic_precipitation m_precipitation;
|
||||
sound_source m_rainsound { sound_placement::external, -1 };
|
||||
basic_wind m_wind;
|
||||
};
|
||||
|
||||
namespace simulation {
|
||||
|
||||
extern world_environment Environment;
|
||||
|
||||
} // simulation
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
22
simulation/simulationsounds.cpp
Normal file
22
simulation/simulationsounds.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
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 "simulationsounds.h"
|
||||
|
||||
#include "utilities.h"
|
||||
|
||||
namespace simulation {
|
||||
|
||||
sound_overridemap Sound_overrides;
|
||||
sound_table Sounds;
|
||||
|
||||
} // simulation
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
23
simulation/simulationsounds.h
Normal file
23
simulation/simulationsounds.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sound.h"
|
||||
|
||||
namespace simulation {
|
||||
|
||||
using sound_overridemap = std::unordered_map<std::string, std::string>;
|
||||
|
||||
extern sound_overridemap Sound_overrides;
|
||||
extern sound_table Sounds;
|
||||
|
||||
} // simulation
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
1296
simulation/simulationstateserializer.cpp
Normal file
1296
simulation/simulationstateserializer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
93
simulation/simulationstateserializer.h
Normal file
93
simulation/simulationstateserializer.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "parser.h"
|
||||
#include "scene.h"
|
||||
|
||||
namespace simulation {
|
||||
|
||||
struct deserializer_state {
|
||||
std::string scenariofile;
|
||||
cParser input;
|
||||
scene::scratch_data scratchpad;
|
||||
using deserializefunctionbind = std::function<void()>;
|
||||
std::unordered_map<
|
||||
std::string,
|
||||
deserializefunctionbind> functionmap;
|
||||
|
||||
deserializer_state(std::string const &File, cParser::buffertype const Type, const std::string &Path, bool const Loadtraction)
|
||||
: scenariofile(File), input(File, Type, Path, Loadtraction) { }
|
||||
};
|
||||
|
||||
class state_serializer {
|
||||
|
||||
public:
|
||||
|
||||
// methods
|
||||
// starts deserialization from specified file, returns context pointer on success, throws otherwise
|
||||
std::shared_ptr<deserializer_state>
|
||||
deserialize_begin(std::string const &Scenariofile);
|
||||
// continues deserialization for given context, amount limited by time, returns true if needs to be called again
|
||||
bool
|
||||
deserialize_continue(std::shared_ptr<deserializer_state> state);
|
||||
// stores class data in specified file, in legacy (text) format
|
||||
void
|
||||
export_as_text( std::string const &Scenariofile ) const;
|
||||
// create new model from node stirng
|
||||
TAnimModel * create_model(std::string const &src, std::string const &name, const glm::dvec3 &position);
|
||||
// create new eventlauncher from node stirng
|
||||
TEventLauncher * create_eventlauncher(std::string const &src, std::string const &name, const glm::dvec3 &position);
|
||||
|
||||
private:
|
||||
// methods
|
||||
// restores class data from provided stream
|
||||
void deserialize_area( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_isolated( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_assignment( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_atmo( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_camera( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_config( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_description( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_event( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_lua( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_firstinit( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_group( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_endgroup( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_light( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_node( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_origin( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_endorigin( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_rotate( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_sky( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_test( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_time( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_trainset( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_terrain( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
void deserialize_endtrainset( cParser &Input, scene::scratch_data &Scratchpad );
|
||||
TTrack * deserialize_path( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
|
||||
TTraction * deserialize_traction( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
|
||||
TTractionPowerSource * deserialize_tractionpowersource( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
|
||||
TMemCell * deserialize_memorycell( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
|
||||
TEventLauncher * deserialize_eventlauncher( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
|
||||
TAnimModel * deserialize_model( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
|
||||
TDynamicObject * deserialize_dynamic( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
|
||||
sound_source * deserialize_sound( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
|
||||
void init_time();
|
||||
// skips content of stream until specified token
|
||||
void skip_until( cParser &Input, std::string const &Token );
|
||||
// transforms provided location by specifed rotation and offset
|
||||
glm::dvec3 transform( glm::dvec3 Location, scene::scratch_data const &Scratchpad );
|
||||
void export_nodes_to_stream( std::ostream &, bool Dirty ) const;
|
||||
};
|
||||
|
||||
} // simulation
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
257
simulation/simulationtime.cpp
Normal file
257
simulation/simulationtime.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
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 "simulationtime.h"
|
||||
|
||||
#include "Globals.h"
|
||||
#include "utilities.h"
|
||||
|
||||
namespace simulation {
|
||||
|
||||
scenario_time Time;
|
||||
|
||||
} // simulation
|
||||
|
||||
void
|
||||
scenario_time::init(std::time_t timestamp) {
|
||||
char monthdaycounts[ 2 ][ 13 ] = {
|
||||
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
|
||||
{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } };
|
||||
::memcpy( m_monthdaycounts, monthdaycounts, sizeof( monthdaycounts ) );
|
||||
|
||||
// potentially adjust scenario clock
|
||||
auto const requestedtime { clamp_circular<int>( m_time.wHour * 60 + m_time.wMinute + Global.ScenarioTimeOffset * 60, 24 * 60 ) };
|
||||
auto const requestedhour { ( requestedtime / 60 ) % 24 };
|
||||
auto const requestedminute { requestedtime % 60 };
|
||||
// cache requested elements, if any
|
||||
|
||||
std::tm *tms = std::gmtime(×tamp);
|
||||
m_time.wYear = tms->tm_year + 1900;
|
||||
m_time.wMonth = tms->tm_mon + 1;
|
||||
m_time.wDayOfWeek = tms->tm_wday;
|
||||
m_time.wDay = tms->tm_mday;
|
||||
m_time.wHour = tms->tm_hour;
|
||||
m_time.wMinute = tms->tm_min;
|
||||
m_time.wSecond = tms->tm_sec;
|
||||
m_time.wMilliseconds = 0;
|
||||
|
||||
if( Global.fMoveLight > 0.0 ) {
|
||||
// day and month of the year can be overriden by scenario setup
|
||||
daymonth( m_time.wDay, m_time.wMonth, m_time.wYear, static_cast<WORD>( Global.fMoveLight ) );
|
||||
}
|
||||
|
||||
if( requestedhour != -1 ) { m_time.wHour = static_cast<WORD>( clamp( requestedhour, 0, 23 ) ); }
|
||||
if( requestedminute != -1 ) { m_time.wMinute = static_cast<WORD>( clamp( requestedminute, 0, 59 ) ); }
|
||||
// if the time is taken from the local clock leave the seconds intact, otherwise set them to zero
|
||||
if( ( requestedhour != -1 )
|
||||
|| ( requestedminute != 1 ) ) {
|
||||
m_time.wSecond = 0;
|
||||
}
|
||||
|
||||
m_yearday = year_day( m_time.wDay, m_time.wMonth, m_time.wYear );
|
||||
|
||||
// calculate time zone bias
|
||||
// retrieve relevant time zone info from system registry (or fall back on supplied default)
|
||||
// TODO: select timezone matching defined geographic location and/or country
|
||||
struct registry_time_zone_info {
|
||||
long Bias;
|
||||
long StandardBias;
|
||||
long DaylightBias;
|
||||
SYSTEMTIME StandardDate;
|
||||
SYSTEMTIME DaylightDate;
|
||||
} timezoneinfo = { -60, 0, -60, { 0, 10, 0, 5, 3, 0, 0, 0 }, { 0, 3, 0, 5, 2, 0, 0, 0 } };
|
||||
|
||||
convert_transition_time( timezoneinfo.StandardDate );
|
||||
convert_transition_time( timezoneinfo.DaylightDate );
|
||||
|
||||
auto zonebias { timezoneinfo.Bias };
|
||||
if( m_yearday < year_day( timezoneinfo.DaylightDate.wDay, timezoneinfo.DaylightDate.wMonth, m_time.wYear ) ) {
|
||||
zonebias += timezoneinfo.StandardBias;
|
||||
}
|
||||
else if( m_yearday < year_day( timezoneinfo.StandardDate.wDay, timezoneinfo.StandardDate.wMonth, m_time.wYear ) ) {
|
||||
zonebias += timezoneinfo.DaylightBias;
|
||||
}
|
||||
else {
|
||||
zonebias += timezoneinfo.StandardBias;
|
||||
}
|
||||
|
||||
m_timezonebias = ( zonebias / 60.0 );
|
||||
}
|
||||
|
||||
void
|
||||
scenario_time::update( double const Deltatime ) {
|
||||
|
||||
m_milliseconds += ( 1000.0 * Deltatime );
|
||||
while( m_milliseconds >= 1000.0 ) {
|
||||
|
||||
++m_time.wSecond;
|
||||
m_milliseconds -= 1000.0;
|
||||
}
|
||||
m_time.wMilliseconds = std::floor( m_milliseconds );
|
||||
while( m_time.wSecond >= 60 ) {
|
||||
|
||||
++m_time.wMinute;
|
||||
m_time.wSecond -= 60;
|
||||
}
|
||||
while( m_time.wMinute >= 60 ) {
|
||||
|
||||
++m_time.wHour;
|
||||
m_time.wMinute -= 60;
|
||||
}
|
||||
while( m_time.wHour >= 24 ) {
|
||||
|
||||
++m_time.wDay;
|
||||
++m_time.wDayOfWeek;
|
||||
if( m_time.wDayOfWeek >= 7 ) {
|
||||
m_time.wDayOfWeek -= 7;
|
||||
}
|
||||
m_time.wHour -= 24;
|
||||
}
|
||||
int leap { is_leap( m_time.wYear ) };
|
||||
while( m_time.wDay > m_monthdaycounts[ leap ][ m_time.wMonth ] ) {
|
||||
|
||||
m_time.wDay -= m_monthdaycounts[ leap ][ m_time.wMonth ];
|
||||
++m_time.wMonth;
|
||||
// unlikely but we might've entered a new year
|
||||
if( m_time.wMonth > 12 ) {
|
||||
|
||||
++m_time.wYear;
|
||||
leap = is_leap( m_time.wYear );
|
||||
m_time.wMonth -= 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
scenario_time::year_day( int Day, const int Month, const int Year ) const {
|
||||
|
||||
char const daytab[ 2 ][ 13 ] = {
|
||||
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
|
||||
{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
|
||||
};
|
||||
|
||||
int const leap { is_leap( Year ) };
|
||||
for( int i = 1; i < Month; ++i )
|
||||
Day += daytab[ leap ][ i ];
|
||||
|
||||
return Day;
|
||||
}
|
||||
|
||||
void
|
||||
scenario_time::daymonth( WORD &Day, WORD &Month, WORD const Year, WORD const Yearday ) {
|
||||
|
||||
WORD daytab[ 2 ][ 13 ] = {
|
||||
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
|
||||
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
|
||||
};
|
||||
|
||||
int const leap { is_leap( Year ) };
|
||||
WORD idx = 1;
|
||||
while( ( idx < 13 ) && ( Yearday >= daytab[ leap ][ idx ] ) ) {
|
||||
|
||||
++idx;
|
||||
}
|
||||
Month = idx;
|
||||
Day = Yearday - daytab[ leap ][ idx - 1 ];
|
||||
}
|
||||
|
||||
int
|
||||
scenario_time::julian_day() const {
|
||||
|
||||
int yy = ( m_time.wYear < 0 ? m_time.wYear + 1 : m_time.wYear ) - std::floor( ( 12 - m_time.wMonth ) / 10.f );
|
||||
int mm = m_time.wMonth + 9;
|
||||
if( mm >= 12 ) { mm -= 12; }
|
||||
|
||||
int K1 = std::floor( 365.25 * ( yy + 4712 ) );
|
||||
int K2 = std::floor( 30.6 * mm + 0.5 );
|
||||
|
||||
// for dates in Julian calendar
|
||||
int JD = K1 + K2 + m_time.wDay + 59;
|
||||
// for dates in Gregorian calendar; 2299160 is October 15th, 1582
|
||||
const int gregorianswitchday = 2299160;
|
||||
if( JD > gregorianswitchday ) {
|
||||
|
||||
int K3 = std::floor( std::floor( ( yy * 0.01 ) + 49 ) * 0.75 ) - 38;
|
||||
JD -= K3;
|
||||
}
|
||||
|
||||
return JD;
|
||||
}
|
||||
|
||||
void scenario_time::set_time(int yearday, int minute) {
|
||||
m_yearday = yearday;
|
||||
daymonth(m_time.wDay, m_time.wMonth, m_time.wYear, m_yearday);
|
||||
m_time.wHour = minute / 60;
|
||||
m_time.wMinute = minute % 60;
|
||||
}
|
||||
|
||||
// calculates day of week for provided date
|
||||
int
|
||||
scenario_time::day_of_week( int const Day, int const Month, int const Year ) const {
|
||||
|
||||
// using Zeller's congruence, http://en.wikipedia.org/wiki/Zeller%27s_congruence
|
||||
int const q = Day;
|
||||
int const m = Month > 2 ? Month : Month + 12;
|
||||
int const y = Month > 2 ? Year : Year - 1;
|
||||
|
||||
int const h = ( q + ( 26 * ( m + 1 ) / 10 ) + y + ( y / 4 ) + 6 * ( y / 100 ) + ( y / 400 ) ) % 7;
|
||||
|
||||
/* return ( (h + 5) % 7 ) + 1; // iso week standard, with monday = 1
|
||||
*/ return ( (h + 6) % 7 ) + 1; // sunday = 1 numbering method, used in north america, japan
|
||||
}
|
||||
|
||||
// calculates day of month for specified weekday of specified month of the year
|
||||
int
|
||||
scenario_time::day_of_month( int const Week, int const Weekday, int const Month, int const Year ) const {
|
||||
|
||||
int day = 0;
|
||||
int dayoffset = weekdays( day_of_week( 1, Month, Year ), Weekday );
|
||||
|
||||
day = ( Week - 1 ) * 7 + 1 + dayoffset;
|
||||
|
||||
if( Week == 5 ) {
|
||||
// 5th week potentially indicates last week in the month, not necessarily actual 5th
|
||||
char const daytab[ 2 ][ 13 ] = {
|
||||
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
|
||||
{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
|
||||
};
|
||||
int const leap { is_leap( Year ) };
|
||||
|
||||
while( day > daytab[ leap ][ Month ] ) {
|
||||
day -= 7;
|
||||
}
|
||||
}
|
||||
|
||||
return day;
|
||||
}
|
||||
|
||||
// returns number of days between specified days of week
|
||||
int
|
||||
scenario_time::weekdays( int const First, int const Second ) const {
|
||||
|
||||
if( Second >= First ) { return Second - First; }
|
||||
else { return 7 - First + Second; }
|
||||
}
|
||||
|
||||
// helper, converts provided time transition date to regular date
|
||||
void
|
||||
scenario_time::convert_transition_time( SYSTEMTIME &Time ) const {
|
||||
|
||||
// NOTE: windows uses 0-6 range for days of week numbering, our methods use 1-7
|
||||
Time.wDay = day_of_month( Time.wDay, Time.wDayOfWeek + 1, Time.wMonth, m_time.wYear );
|
||||
}
|
||||
|
||||
bool
|
||||
scenario_time::is_leap( int const Year ) const {
|
||||
|
||||
return ( ( Year % 4 == 0 ) && ( ( Year % 100 != 0 ) || ( Year % 400 == 0 ) ) );
|
||||
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
89
simulation/simulationtime.h
Normal file
89
simulation/simulationtime.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "winheaders.h"
|
||||
|
||||
// wrapper for scenario time
|
||||
class scenario_time {
|
||||
|
||||
public:
|
||||
scenario_time() {
|
||||
m_time.wHour = 10; m_time.wMinute = 30; }
|
||||
void
|
||||
init(time_t timestamp = 0);
|
||||
void
|
||||
update( double const Deltatime );
|
||||
inline
|
||||
SYSTEMTIME &
|
||||
data() {
|
||||
return m_time; }
|
||||
inline
|
||||
SYSTEMTIME const &
|
||||
data() const {
|
||||
return m_time; }
|
||||
inline
|
||||
double
|
||||
second() const {
|
||||
return ( m_time.wMilliseconds * 0.001 + m_time.wSecond ); }
|
||||
inline
|
||||
int
|
||||
year_day() const {
|
||||
return m_yearday; }
|
||||
int
|
||||
julian_day() const;
|
||||
inline
|
||||
double
|
||||
zone_bias() const {
|
||||
return m_timezonebias; }
|
||||
void
|
||||
set_time(int yearday, int minute);
|
||||
|
||||
/** Returns std::string in format: `"mm:ss"`. */
|
||||
operator std::string();
|
||||
private:
|
||||
// converts provided time transition date to regular date
|
||||
void
|
||||
convert_transition_time( SYSTEMTIME &Time ) const;
|
||||
// calculates day and month from given day of year
|
||||
void
|
||||
daymonth( WORD &Day, WORD &Month, WORD const Year, WORD const Yearday );
|
||||
// calculates day of year from given date
|
||||
int
|
||||
year_day( int Day, int const Month, int const Year ) const;
|
||||
// calculates day of week for provided date
|
||||
int
|
||||
day_of_week( int const Day, int const Month, int const Year ) const;
|
||||
// calculates day of month for specified weekday of specified month of the year
|
||||
int
|
||||
day_of_month( int const Week, int const Weekday, int const Month, int const Year ) const;
|
||||
// returns number of days between specified days of week
|
||||
int
|
||||
weekdays( int const First, int const Second ) const;
|
||||
// returns true if specified year is a leap year, false otherwise
|
||||
bool
|
||||
is_leap( int const Year ) const;
|
||||
|
||||
SYSTEMTIME m_time;
|
||||
double m_milliseconds{ 0.0 };
|
||||
int m_yearday;
|
||||
char m_monthdaycounts[ 2 ][ 13 ];
|
||||
double m_timezonebias{ 0.0 };
|
||||
};
|
||||
|
||||
namespace simulation {
|
||||
|
||||
extern scenario_time Time;
|
||||
|
||||
} // simulation
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user