first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

17
src/Time/CMakeLists.txt Normal file
View File

@@ -0,0 +1,17 @@
include(FlightGearComponent)
set(SOURCES
TimeManager.cxx
light.cxx
tide.cxx
bodysolver.cxx
)
set(HEADERS
TimeManager.hxx
light.hxx
tide.hxx
bodysolver.hxx
)
flightgear_component(Time "${SOURCES}" "${HEADERS}")

675
src/Time/TimeManager.cxx Normal file
View File

@@ -0,0 +1,675 @@
// TimeManager.cxx -- simulation-wide time management
//
// Written by James Turner, started July 2010.
//
// Copyright (C) 2010 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "TimeManager.hxx"
#include <simgear/misc/sg_path.hxx>
#include <simgear/timing/lowleveltime.h>
#include <simgear/structure/commands.hxx>
#include <simgear/timing/sg_time.hxx>
#include <simgear/math/SGMath.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Time/bodysolver.hxx>
#include <algorithm>
#include <chrono>
#include <thread>
static bool do_timeofday (const SGPropertyNode * arg, SGPropertyNode * root)
{
const std::string &offset_type = arg->getStringValue("timeofday", "noon");
int offset = arg->getIntValue("offset", 0);
TimeManager* self = (TimeManager*) globals->get_subsystem("time");
if (offset_type == "real") {
// without this, setting 'real' time is a no-op, since the current
// wrap value (orig_warp) is retained in setTimeOffset. Ick.
fgSetInt("/sim/time/warp", 0);
}
self->setTimeOffset(offset_type, offset);
return true;
}
TimeManager::TimeManager() :
_inited(false),
_impl(NULL)
{
globals->get_commands()->addCommand("timeofday", do_timeofday);
}
TimeManager::~TimeManager()
{
globals->get_commands()->removeCommand("timeofday");
}
void TimeManager::init()
{
if (_inited) {
// time manager has to be initialised early, so needs to be defensive
// about multiple initialisation
return;
}
_firstUpdate = true;
_inited = true;
_dtRemainder = 0.0;
_mpProtocolClock = _steadyClock = 0.0;
_adjustWarpOnUnfreeze = false;
_maxDtPerFrame = fgGetNode("/sim/max-simtime-per-frame", true);
_clockFreeze = fgGetNode("/sim/freeze/clock", true);
_timeOverride = fgGetNode("/sim/time/cur-time-override", true);
_warp = fgGetNode("/sim/time/warp", true);
_warp->addChangeListener(this);
_maxFrameRate = fgGetNode("/sim/frame-rate-throttle-hz", true);
_localTimeStringNode = fgGetNode("/sim/time/local-time-string", true);
_localTimeZoneNode = fgGetNode("/sim/time/local-timezone", true);
_warpDelta = fgGetNode("/sim/time/warp-delta", true);
_frameNumber = fgGetNode("/sim/frame-number", true);
_simFixedDt = fgGetNode("/sim/time/fixed-dt", true);
SGPath zone(globals->get_fg_root());
zone.append("Timezone");
_impl = new SGTime(globals->get_aircraft_position(), zone, _timeOverride->getLongValue());
_warpDelta->setDoubleValue(0.0);
updateLocalTime();
_impl->update(globals->get_aircraft_position(), _timeOverride->getLongValue(),
_warp->getIntValue());
globals->set_time_params(_impl);
// frame-rate / worst-case latency / update-rate counters
_frameRate = fgGetNode("/sim/frame-rate", true);
_frameLatency = fgGetNode("/sim/frame-latency-max-ms", true);
_frameRateWorst = fgGetNode("/sim/frame-rate-worst", true);
_lastFrameTime = 0;
_frameLatencyMax = 0.0;
_frameCount = 0;
_sceneryLoaded = fgGetNode("sim/sceneryloaded", true);
_modelHz = fgGetNode("sim/model-hz", true);
_timeDelta = fgGetNode("sim/time/delta-realtime-sec", true);
_simTimeDelta = fgGetNode("sim/time/delta-sec", true);
_mpProtocolClockNode = fgGetNode("sim/time/mp-clock-sec", true);
_steadyClockNode = fgGetNode("sim/time/steady-clock-sec", true);
_frameTimeOffsetNode = fgGetNode("sim/time/frame-time-offset-ms", true);
_dtRemainderNode = fgGetNode("sim/time/dt-remainder-sec", true);
_mpClockOffset = fgGetNode("sim/time/mp-clock-offset-sec", true);
_steadyClockDrift = fgGetNode("sim/time/steady-clock-drift-ms", true);
_computeDrift = fgGetNode("sim/time/compute-clock-drift", true);
_frameWait = fgGetNode("sim/time/frame-wait-ms", true);
_simTimeFactor = fgGetNode("/sim/speed-up", true);
// use pre-set value but ensure we get a sane default
if (!_simTimeDelta->hasValue()) {
_simTimeFactor->setDoubleValue(1.0);
}
if (!_mpClockOffset->hasValue()) {
_mpClockOffset->setDoubleValue(0.0);
}
_computeDrift->setBoolValue(true);
_simpleTimeEnabledPrev = false;
_simpleTimeEnabled = fgGetNode("/sim/time/simple-time/enabled", true);
_simpleTimeUtc = fgGetNode("/sim/time/simple-time/utc", true);
_simpleTimeFdm = fgGetNode("/sim/time/simple-time/fdm", true);
_simple_time_utc = 0;
_simple_time_fdm = 0;
}
void TimeManager::unbind()
{
_maxDtPerFrame.clear();
_clockFreeze.clear();
_timeOverride.clear();
_warp.clear();
_warpDelta.clear();
_frameRate.clear();
_frameLatency.clear();
_frameRateWorst.clear();
_frameWait.clear();
_maxFrameRate.clear();
_sceneryLoaded.clear();
_modelHz.clear();
_timeDelta.clear();
_simTimeDelta.clear();
_mpProtocolClockNode.clear();
_steadyClockNode.clear();
_frameTimeOffsetNode.clear();
_dtRemainderNode.clear();
_mpClockOffset.clear();
_steadyClockDrift.clear();
_computeDrift.clear();
_simTimeFactor.clear();
}
void TimeManager::postinit()
{
initTimeOffset();
}
void TimeManager::reinit()
{
shutdown();
init();
postinit();
}
void TimeManager::shutdown()
{
_warp->removeChangeListener(this);
globals->set_time_params(NULL);
delete _impl;
_impl = NULL;
_inited = false;
}
void TimeManager::valueChanged(SGPropertyNode* aProp)
{
if (aProp == _warp) {
if (_clockFreeze->getBoolValue()) {
// if the warp is changed manually while frozen, don't modify it when
// un-freezing - the user wants to unfreeze with exactly the warp
// they specified.
_adjustWarpOnUnfreeze = false;
}
_impl->update(globals->get_aircraft_position(),
_timeOverride->getLongValue(),
_warp->getIntValue());
}
}
// simple-time mode requires UTC time.
//
// SGTimeStamp() doesn't return UTC time on some systems, e.g. Linux with
// _POSIX_TIMERS > 0 uses _POSIX_MONOTONIC_CLOCK if available.
//
// So we define our own time function here.
//
static double TimeUTC()
{
auto t = std::chrono::system_clock::now().time_since_epoch();
typedef std::chrono::duration<double, std::ratio<1, 1>> duration_hz_fp;
auto ret = std::chrono::duration_cast<duration_hz_fp>(t);
return ret.count();
}
void TimeManager::computeTimeDeltasSimple(double& simDt, double& realDt)
{
double t;
double fixed_dt = _simFixedDt->getDoubleValue();
static double fixed_dt_prev = 0.0;
if (fixed_dt)
{
// Always increase time by fixed amount, regardless of elapsed
// time. E.g. this can be used to generate high-quality videos.
t = _simple_time_fdm + fixed_dt;
fixed_dt_prev = fixed_dt;
}
else
{
t = TimeUTC();
if (fixed_dt_prev)
{
// We are changing from fixed-dt mode to normal mode; avoid bogus
// sleep to match _maxFrameRate, otherwise we can end up pausing
// for a long time.
_simple_time_fdm = _simple_time_utc = t - fixed_dt_prev;
fixed_dt_prev = 0.0;
}
}
double modelHz = _modelHz->getDoubleValue();
bool scenery_loaded = _sceneryLoaded->getBoolValue();
if (_firstUpdate) {
_firstUpdate = false;
_simple_time_utc = t;
_simple_time_fdm = t;
SGSubsystemGroup* fdmGroup = globals->get_subsystem_mgr()->get_group(SGSubsystemMgr::FDM);
fdmGroup->set_fixed_update_time(1.0 / modelHz);
}
// Sleep if necessary to respect _maxFrameRate. It's simpler to do this
// inline instead of calling throttleUpdateRate().
//
double sleep_time = 0;
if (scenery_loaded && !fixed_dt) {
double max_frame_rate = _maxFrameRate->getDoubleValue();
if (max_frame_rate != 0) {
double delay_end = _simple_time_utc + 1.0/max_frame_rate;
if (delay_end > t) {
sleep_time = delay_end - t;
std::this_thread::sleep_for(std::chrono::milliseconds((int) (sleep_time * 1000)));
t = delay_end;
}
}
}
else {
// suppress framerate while initial scenery isn't loaded yet (splash screen still active)
_lastFrameTime=0;
_frameCount = 0;
}
// Increment <_simple_time_fdm> by a multiple of the FDM interval, such
// that it is as close as possible, but not greater than, the current UTC
// time <t>.
//
double dt_fdm = floor( (t - _simple_time_fdm) * modelHz) / modelHz;
_simple_time_fdm += dt_fdm;
_frameLatencyMax = std::max(_frameLatencyMax, t - _simple_time_utc);
_simple_time_utc = t;
_simpleTimeUtc->setDoubleValue(_simple_time_utc);
_simpleTimeFdm->setDoubleValue(_simple_time_fdm);
// simDt defaults to dt_fdm, but is affected by whether we are paused or
// running the FDM at faster/slowe than normal.
if (_clockFreeze->getBoolValue() || !scenery_loaded) {
simDt = 0;
}
else {
simDt = dt_fdm * _simTimeFactor->getDoubleValue();
}
realDt = dt_fdm;
globals->inc_sim_time_sec(simDt);
_mpProtocolClock = _simple_time_fdm;
_mpProtocolClockNode->setDoubleValue(_mpProtocolClock);
// Not sure anyone calls getSteadyClockSec()?
_steadyClock = _simple_time_fdm;
_steadyClockNode->setDoubleValue(_steadyClock);
// These are used by Nasal scripts, e.g. when interpolating property
// values.
_timeDelta->setDoubleValue(realDt);
_simTimeDelta->setDoubleValue(simDt);
SG_LOG(SG_GENERAL, SG_DEBUG, ""
<< std::setprecision(5)
<< std::fixed
<< std::setw(16)
<< " " << ((simDt >= 1.0) ? "*" : " ")
<< " simDt=" << simDt
<< " realDt=" << realDt
<< " sleep_time=" << sleep_time
<< " _simple_time_utc=" << _simple_time_utc
<< " _simple_time_fdm=" << _simple_time_fdm
<< " utc-fdm=" << (_simple_time_utc - _simple_time_fdm)
<< " _steadyClock=" << _steadyClock
<< " _mpProtocolClock=" << _mpProtocolClock
);
}
void TimeManager::computeTimeDeltas(double& simDt, double& realDt)
{
bool simple_time = _simpleTimeEnabled->getBoolValue();
if (simple_time != _simpleTimeEnabledPrev) {
_simpleTimeEnabledPrev = simple_time;
_firstUpdate = true;
}
if (simple_time) {
computeTimeDeltasSimple(simDt, realDt);
return;
}
const double modelHz = _modelHz->getDoubleValue();
// Update the elapsed time.
if (_firstUpdate) {
_lastStamp.stamp();
// Initialise the mp protocol / steady clock with the system clock.
// later, the clock follows steps of 1/modelHz (120 by default),
// so the MP clock remains aligned to these boundaries
_systemStamp.systemClockHoursAndMinutes();
const double systemStamp = _systemStamp.toSecs();
_steadyClock = floor(systemStamp * modelHz) / modelHz;
// add offset if defined
const double frameOffsetMsec = _frameTimeOffsetNode->getDoubleValue();
_steadyClock += frameOffsetMsec / 1000.0;
// initialize the remainder with offset from the system clock
_dtRemainder = systemStamp - _steadyClock;
_firstUpdate = false;
_lastClockFreeze = _clockFreeze->getBoolValue();
}
bool wait_for_scenery = !_sceneryLoaded->getBoolValue();
if (!wait_for_scenery) {
throttleUpdateRate();
} else {
// suppress framerate while initial scenery isn't loaded yet (splash screen still active)
_lastFrameTime=0;
_frameCount = 0;
}
SGTimeStamp currentStamp;
currentStamp.stamp();
// if asked, we compute the drift between the steady clock and the system clock
if (_computeDrift->getBoolValue()) {
_systemStamp.systemClockHoursAndMinutes();
double clockdrift = _steadyClock + (currentStamp - _lastStamp).toSecs()
+ _dtRemainder - _systemStamp.toSecs();
_steadyClockDrift->setDoubleValue(clockdrift * 1000.0);
_computeDrift->setBoolValue(false);
}
// this dt will be clamped by the max sim time by frame.
double fixed_dt = _simFixedDt->getDoubleValue();
double dt = (fixed_dt) ? fixed_dt : (currentStamp - _lastStamp).toSecs();
// here we have a true real dt for a clock "real time".
double mpProtocolDt = dt;
if (dt > _frameLatencyMax)
_frameLatencyMax = dt;
// Limit the time we need to spend in simulation loops
// That means, if the /sim/max-simtime-per-frame value is strictly positive
// you can limit the maximum amount of time you will do simulations for
// one frame to display. The cpu time spent in simulations code is roughly
// at least O(real_delta_time_sec). If this is (due to running debug
// builds or valgrind or something different blowing up execution times)
// larger than the real time you will no longer get any response
// from flightgear. This limits that effect. Just set to property from
// your .fgfsrc or commandline ...
double dtMax = _maxDtPerFrame->getDoubleValue();
if (0 < dtMax && dtMax < dt) {
dt = dtMax;
}
SGSubsystemGroup* fdmGroup =
globals->get_subsystem_mgr()->get_group(SGSubsystemMgr::FDM);
fdmGroup->set_fixed_update_time(1.0 / modelHz);
// round the real time down to a multiple of 1/model-hz.
// this way all systems are updated the _same_ amount of dt.
dt += _dtRemainder;
// we keep the mp clock sync with the sim time, as it's used as timestamp
// in fdm state,
mpProtocolDt += _dtRemainder;
int multiLoop = long(floor(dt * modelHz));
multiLoop = SGMisc<long>::max(0, multiLoop);
_dtRemainder = dt - double(multiLoop)/modelHz;
dt = double(multiLoop)/modelHz;
mpProtocolDt -= _dtRemainder;
realDt = dt;
if (_clockFreeze->getBoolValue() || wait_for_scenery) {
simDt = 0;
} else {
// sim time can be scaled
simDt = dt * _simTimeFactor->getDoubleValue();
}
_lastStamp = currentStamp;
globals->inc_sim_time_sec(simDt);
_steadyClock += mpProtocolDt;
_mpProtocolClock = _steadyClock + _mpClockOffset->getDoubleValue();
_dtRemainderNode->setDoubleValue(_dtRemainder);
_steadyClockNode->setDoubleValue(_steadyClock);
_mpProtocolClockNode->setDoubleValue(_mpProtocolClock);
// These are useful, especially for Nasal scripts.
_timeDelta->setDoubleValue(realDt);
_simTimeDelta->setDoubleValue(simDt);
}
void TimeManager::update(double dt)
{
_frameNumber->setIntValue(_frameNumber->getIntValue() + 1);
bool freeze = _clockFreeze->getBoolValue();
time_t now = time(NULL);
if (freeze) {
// clock freeze requested
if (_timeOverride->getLongValue() == 0) {
_timeOverride->setLongValue(now);
_adjustWarpOnUnfreeze = true;
}
} else {
// no clock freeze requested
if (_lastClockFreeze) {
if (_adjustWarpOnUnfreeze) {
// clock just unfroze, let's set warp as the difference
// between frozen time and current time so we don't get a
// time jump (and corresponding sky object and lighting
// jump.)
int adjust = _timeOverride->getLongValue() - now;
SG_LOG(SG_GENERAL, SG_DEBUG, "adjusting on un-freeze:" << adjust);
_warp->setIntValue(_warp->getIntValue() + adjust);
}
_timeOverride->setLongValue(0);
}
// account for speed-up in warp value. This implies when speed-up is not
// 1.0 we need to continually adjust warp, either forwards for speed-up,
// or backwards for a slow-down. Eg for a speed up of 4x, we want to
// incease warp by 3 additional seconds per elapsed real second.
// for a 1/2x factor, we want to decrease warp by half a second per
// elapsed real second.
double speedUp = _simTimeFactor->getDoubleValue() - 1.0;
if (speedUp != 0.0) {
double realDt = _timeDelta->getDoubleValue();
double speedUpOffset = speedUp * realDt;
_warp->setDoubleValue(_warp->getDoubleValue() + speedUpOffset);
}
} // of sim not frozen
// scale warp-delta by real-dt, so rate is constant with frame-rate,
// but warping works while paused
int warpDelta = _warpDelta->getIntValue();
if (warpDelta) {
_adjustWarpOnUnfreeze = false;
double warpOffset = warpDelta * _timeDelta->getDoubleValue();
_warp->setDoubleValue(_warp->getDoubleValue() + warpOffset);
}
const auto d2 = distSqr(_lastTimeZoneCheckPosition, globals->get_aircraft_position_cart());
const auto oneNmSqr = SG_NM_TO_METER * SG_NM_TO_METER;
if (d2 > oneNmSqr) {
updateLocalTime();
}
_lastClockFreeze = freeze;
_impl->update(globals->get_aircraft_position(),
_timeOverride->getLongValue(),
_warp->getIntValue());
updateLocalTimeString();
computeFrameRate();
}
void TimeManager::computeFrameRate()
{
// Calculate frame rate average
if ((_impl->get_cur_time() != _lastFrameTime)) {
_frameRate->setIntValue(_frameCount);
_frameLatency->setDoubleValue(_frameLatencyMax*1000);
if (_frameLatencyMax>0)
_frameRateWorst->setIntValue(1/_frameLatencyMax);
_frameCount = 0;
_frameLatencyMax = 0.0;
}
_lastFrameTime = _impl->get_cur_time();
++_frameCount;
}
void TimeManager::throttleUpdateRate()
{
const double throttleHz = _maxFrameRate->getDoubleValue();
// no delay required.
if (throttleHz <= 0) {
_frameWait->setDoubleValue(0);
return;
}
const double modelHz = _modelHz->getDoubleValue();
SGTimeStamp frameWaitStart = SGTimeStamp::now();
// we want to sleep until just after the next ideal timestamp wanted, we will
// gain time from a 1/Hz step if the last timestamp was late.
const double t = (round(modelHz / throttleHz) / modelHz) - _dtRemainder;
SGTimeStamp::sleepUntil(_lastStamp + SGTimeStamp::fromSec(t));
_frameWait->setDoubleValue(frameWaitStart.elapsedMSec());
}
void TimeManager::reposition()
{
// force a zone check, next update()
_lastTimeZoneCheckPosition = SGVec3d::zeros();
}
// periodic time updater wrapper
void TimeManager::updateLocalTime()
{
_lastTimeZoneCheckPosition = globals->get_aircraft_position_cart();
_impl->updateLocal(globals->get_aircraft_position(), globals->get_fg_root() / "Timezone");
// synchronous update, since somebody might need that
updateLocalTimeString();
}
void TimeManager::updateLocalTimeString()
{
time_t cur_time = _impl->get_cur_time();
if (!_impl->get_zonename()) {
return;
}
struct tm* aircraftLocalTime = fgLocaltime(&cur_time, _impl->get_zonename());
static char buf[16];
snprintf(buf, 16, "%.2d:%.2d:%.2d",
aircraftLocalTime->tm_hour,
aircraftLocalTime->tm_min, aircraftLocalTime->tm_sec);
// check against current string to avoid changes all the time
string s = _localTimeStringNode->getStringValue();
if (s != string(buf)) {
_localTimeStringNode->setStringValue(buf);
}
string zs = _localTimeZoneNode->getStringValue();
if (zs != string(_impl->get_description())) {
_localTimeZoneNode->setStringValue(_impl->get_description());
}
}
void TimeManager::initTimeOffset()
{
long int offset = fgGetLong("/sim/startup/time-offset");
std::string offset_type = fgGetString("/sim/startup/time-offset-type");
setTimeOffset(offset_type, offset);
}
void TimeManager::setTimeOffset(const std::string& offset_type, long int offset)
{
// Handle potential user specified time offsets
int orig_warp = _warp->getIntValue();
time_t cur_time = _impl->get_cur_time();
time_t currGMT = sgTimeGetGMT( gmtime(&cur_time) );
time_t systemLocalTime = sgTimeGetGMT( localtime(&cur_time) );
time_t aircraftLocalTime =
sgTimeGetGMT( fgLocaltime(&cur_time, _impl->get_zonename() ) );
// Okay, we now have several possible scenarios
SGGeod loc = globals->get_aircraft_position();
int warp = 0;
if ( offset_type == "real" ) {
warp = 0;
} else if ( offset_type == "dawn" ) {
warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 90.0, true, true );
} else if ( offset_type == "morning" ) {
warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 75.0, true, true );
} else if ( offset_type == "noon" ) {
warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 0.0, true, true );
} else if ( offset_type == "afternoon" ) {
warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 75.0, false, true );
} else if ( offset_type == "dusk" ) {
warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 90.0, false, true );
} else if ( offset_type == "evening" ) {
warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 100.0, false, true );
} else if ( offset_type == "midnight" ) {
warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 180.0, false, true );
} else if ( offset_type == "system-offset" ) {
warp = offset;
orig_warp = 0;
} else if ( offset_type == "gmt-offset" ) {
warp = offset - (currGMT - systemLocalTime);
orig_warp = 0;
} else if ( offset_type == "latitude-offset" ) {
warp = offset - (aircraftLocalTime - systemLocalTime);
orig_warp = 0;
} else if ( offset_type == "system" ) {
warp = offset - (systemLocalTime - currGMT) - cur_time;
} else if ( offset_type == "gmt" ) {
warp = offset - cur_time;
} else if ( offset_type == "latitude" ) {
warp = offset - (aircraftLocalTime - currGMT)- cur_time;
} else {
SG_LOG( SG_GENERAL, SG_ALERT,
"TimeManager::setTimeOffset: unsupported offset: " << offset_type );
warp = 0;
}
if( fgGetBool("/sim/time/warp-easing", false) && !fgGetBool("/devices/status/keyboard/ctrl", false)) {
double duration = fgGetDouble("/sim/time/warp-easing-duration-secs", 5.0 );
const std::string easing = fgGetString("/sim/time/warp-easing-method", "swing" );
SGPropertyNode n;
n.setDoubleValue( orig_warp + warp );
_warp->interpolate( "numeric", n, duration, easing );
} else {
_warp->setIntValue( orig_warp + warp );
}
SG_LOG(SG_GENERAL, SG_INFO, "After TimeManager::setTimeOffset(): " << offset_type << ", warp = " << _warp->getIntValue());
}
double TimeManager::getSimSpeedUpFactor() const
{
return _simTimeFactor->getDoubleValue();
}
// Register the subsystem.
SGSubsystemMgr::Registrant<TimeManager> registrantTimeManager(
SGSubsystemMgr::INIT,
{{"FDM", SGSubsystemMgr::Dependency::HARD}});

142
src/Time/TimeManager.hxx Normal file
View File

@@ -0,0 +1,142 @@
// TimeManager.hxx -- simulation-wide time management
//
// Written by James Turner, started July 2010.
//
// Copyright (C) 2010 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef FG_TIME_TIMEMANAGER_HXX
#define FG_TIME_TIMEMANAGER_HXX
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/math/SGVec3.hxx>
// forward decls
class SGTime;
class TimeManager : public SGSubsystem,
public SGPropertyChangeListener
{
public:
TimeManager();
virtual ~TimeManager();
// Subsystem API.
void init() override;
void postinit() override;
void reinit() override;
void shutdown() override;
void unbind() override;
void update(double dt) override;
void reposition();
// Subsystem identification.
static const char* staticSubsystemClassId() { return "time"; }
void computeTimeDeltas(double& simDt, double& realDt);
void computeTimeDeltasSimple(double& simDt, double& realDt);
// SGPropertyChangeListener overrides
void valueChanged(SGPropertyNode *) override;
void setTimeOffset(const std::string& offset_type, long int offset);
inline double getMPProtocolClockSec() const { return _mpProtocolClock; }
inline double getSteadyClockSec() const { return _steadyClock; }
double getSimSpeedUpFactor() const;
private:
// test class is a friend so we can fake elapsed system time
friend class TimeManagerTests;
/**
* Ensure a consistent update-rate using a combination of
* sleep()-ing and busy-waiting.
*/
void throttleUpdateRate();
/**
* Compute frame (update) rate and write it to a property
*/
void computeFrameRate();
void updateLocalTime();
void updateLocalTimeString();
// set up a time offset (aka warp) if one is specified
void initTimeOffset();
bool _inited = false;
SGTime* _impl = nullptr;
SGTimeStamp _lastStamp;
SGTimeStamp _systemStamp;
bool _firstUpdate = true;
double _dtRemainder = 0;
SGPropertyNode_ptr _maxDtPerFrame;
SGPropertyNode_ptr _clockFreeze;
SGPropertyNode_ptr _timeOverride;
SGPropertyNode_ptr _warp;
SGPropertyNode_ptr _warpDelta;
SGPropertyNode_ptr _simTimeFactor;
SGPropertyNode_ptr _mpProtocolClockNode;
SGPropertyNode_ptr _steadyClockNode;
SGPropertyNode_ptr _frameTimeOffsetNode;
SGPropertyNode_ptr _dtRemainderNode;
SGPropertyNode_ptr _mpClockOffset;
SGPropertyNode_ptr _steadyClockDrift;
SGPropertyNode_ptr _computeDrift;
SGPropertyNode_ptr _frameWait;
SGPropertyNode_ptr _maxFrameRate;
SGPropertyNode_ptr _localTimeStringNode;
SGPropertyNode_ptr _localTimeZoneNode;
SGPropertyNode_ptr _frameNumber;
SGPropertyNode_ptr _simFixedDt;
bool _lastClockFreeze = false;
bool _adjustWarpOnUnfreeze = false;
// frame-rate / worst-case latency / update-rate counters
SGPropertyNode_ptr _frameRate;
SGPropertyNode_ptr _frameRateWorst;
SGPropertyNode_ptr _frameLatency;
time_t _lastFrameTime = 0;
double _frameLatencyMax = 0;
double _mpProtocolClock = 0;
double _steadyClock = 0;
int _frameCount = 0;
// we update TZ after moving more than a threshold distance
SGVec3d _lastTimeZoneCheckPosition;
SGPropertyNode_ptr _sceneryLoaded;
SGPropertyNode_ptr _modelHz;
SGPropertyNode_ptr _timeDelta;
SGPropertyNode_ptr _simTimeDelta;
bool _simpleTimeEnabledPrev = false;
SGPropertyNode_ptr _simpleTimeEnabled;
SGPropertyNode_ptr _simpleTimeUtc;
SGPropertyNode_ptr _simpleTimeFdm;
double _simple_time_utc = 0;
double _simple_time_fdm = 0;
};
#endif // of FG_TIME_TIMEMANAGER_HXX

163
src/Time/bodysolver.cxx Normal file
View File

@@ -0,0 +1,163 @@
/*
* bodysolver.cxx - given a location on earth and a time of day/date,
* find the number of seconds to various solar system body
* positions.
*
* Written by Curtis Olson, started September 2003.
*
* Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <cmath>
#include <ctime>
#include <cassert>
#include <simgear/math/SGMath.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include "bodysolver.hxx"
static const time_t day_secs = 86400;
static const time_t half_day_secs = day_secs / 2;
static const time_t step_secs = 60;
/* given a particular time expressed in side real time at prime
* meridian (GST), compute position on the earth (lat, lon) such that
* solar system body is directly overhead. (lat, lon are reported in
* radians) */
void fgBodyPositionGST(double gst, double& lon, double& lat, bool sun_not_moon) {
/* time_t ssue; seconds since unix epoch */
/* double& lat; (return) latitude */
/* double& lon; (return) longitude */
double tmp;
std::string body = sun_not_moon ? "sun" : "moon";
SGPropertyNode* body_node = fgGetNode("/ephemeris/" + body);
assert(body_node);
double xs = sun_not_moon ? body_node->getDoubleValue("xs")
: body_node->getDoubleValue("xg");
//double ys = body_node->getDoubleValue("ys");
double ye = body_node->getDoubleValue("ye");
double ze = body_node->getDoubleValue("ze");
double ra = atan2(ye, xs);
double dec = atan2(ze, sqrt(xs * xs + ye * ye));
tmp = ra - (SGD_2PI/24)*gst;
double signedPI = (tmp < 0.0) ? -SGD_PI : SGD_PI;
tmp = fmod(tmp+signedPI, SGD_2PI) - signedPI;
lon = tmp;
lat = dec;
}
static double body_angle( const SGTime &t, const SGVec3d& world_up, bool sun_not_moon) {
const char *body = sun_not_moon ? "sun" : "moon";
SG_LOG( SG_EVENT, SG_DEBUG, " Updating " << body << " position" );
SG_LOG( SG_EVENT, SG_DEBUG, " Gst = " << t.getGst() );
double lon, gc_lat;
fgBodyPositionGST( t.getGst(), lon, gc_lat, body );
SGVec3d bodypos = SGVec3d::fromGeoc(SGGeoc::fromRadM(lon, gc_lat,
SGGeodesy::EQURAD));
SG_LOG( SG_EVENT, SG_DEBUG, " t.cur_time = " << t.get_cur_time() );
SG_LOG( SG_EVENT, SG_DEBUG,
" " << body << " geocentric lat = " << gc_lat );
// calculate the body's relative angle to local up
SGVec3d nup = normalize(world_up);
SGVec3d nbody = normalize(bodypos);
// cout << "nup = " << nup[0] << "," << nup[1] << ","
// << nup[2] << endl;
// cout << "nbody = " << nbody[0] << "," << nbody[1] << ","
// << nbody[2] << endl;
double body_angle = acos( dot( nup, nbody ) );
double signedPI = (body_angle < 0.0) ? -SGD_PI : SGD_PI;
body_angle = fmod(body_angle+signedPI, SGD_2PI) - signedPI;
double body_angle_deg = body_angle * SG_RADIANS_TO_DEGREES;
SG_LOG( SG_EVENT, SG_DEBUG, body << " angle relative to current location = "
<< body_angle_deg );
return body_angle_deg;
}
/**
* Given the current unix time in seconds, calculate seconds to the
* specified body angle (relative to straight up.) Also specify if we
* want the angle while the body is ascending or descending. For
* instance noon is when the sun angle is 0 (or the closest it can
* get.) Dusk is when the sun angle is 90 and descending. Dawn is
* when the sun angle is 90 and ascending.
*/
time_t fgTimeSecondsUntilBodyAngle( time_t cur_time,
const SGGeod& loc,
double target_angle_deg,
bool ascending,
bool sun_not_moon )
{
SGVec3d world_up = SGVec3d::fromGeod(loc);
SGTime t = SGTime( loc, SGPath(), 0 );
double best_diff = 180.0;
double last_angle = -99999.0;
time_t best_time = cur_time;
for ( time_t secs = cur_time - half_day_secs;
secs < cur_time + half_day_secs;
secs += step_secs )
{
t.update( loc, secs, 0 );
double angle_deg = body_angle( t, world_up, sun_not_moon );
double diff = fabs( angle_deg - target_angle_deg );
if ( diff < best_diff ) {
if ( last_angle <= 180.0 && ascending
&& ( last_angle > angle_deg ) ) {
// cout << "best angle = " << angle << " offset = "
// << secs - cur_time << endl;
best_diff = diff;
best_time = secs;
} else if ( last_angle <= 180.0 && !ascending
&& ( last_angle < angle_deg ) ) {
// cout << "best angle = " << angle << " offset = "
// << secs - cur_time << endl;
best_diff = diff;
best_time = secs;
}
}
last_angle = angle_deg;
}
return best_time - cur_time;
}

65
src/Time/bodysolver.hxx Normal file
View File

@@ -0,0 +1,65 @@
/*
* bodysolver.hxx - given a location on earth and a time of day/date,
* find the number of seconds to various solar system body
* positions.
*
* Written by Curtis Olson, started September 2003.
*
* Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
#ifndef _BODYSOLVER_HXX
#define _BODYSOLVER_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#include <simgear/compiler.h>
#include <ctime>
class SGGeod;
/**
* Given the current unix time in seconds, calculate seconds to the
* specified solar system body angle (relative to straight up.) Also
* specify if we want the angle while the body is ascending or descending.
* For instance noon is when the sun angle is 0 (or the closest it can
* get.) Dusk is when the sun angle is 90 and descending. Dawn is
* when the sun angle is 90 and ascending.
*/
time_t fgTimeSecondsUntilBodyAngle( time_t cur_time,
const SGGeod& loc,
double target_angle_deg,
bool ascending,
bool sun_not_moon );
/**
* given a particular time expressed in side real time at prime
* meridian (GST), compute position on the earth (lat, lon) such that
* solar system body is directly overhead. (lat, lon are reported in
* radians)
*/
void fgBodyPositionGST(double gst, double& lon, double& lat, bool sun_not_moon);
#endif /* _BODYSOLVER_HXX */

430
src/Time/light.cxx Normal file
View File

@@ -0,0 +1,430 @@
//
// light.cxx -- lighting routines
//
// Written by Curtis Olson, started April 1998.
//
// Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/compiler.h>
#include <cmath>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/interpolater.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/scene/sky/sky.hxx>
#include <simgear/screen/colors.hxx>
#include <simgear/timing/sg_time.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <Main/main.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Viewer/renderer.hxx>
#include <Viewer/view.hxx>
#include "light.hxx"
#include "bodysolver.hxx"
// initialize lighting tables
void FGLight::init () {
SG_LOG( SG_EVENT, SG_INFO,
"Initializing Lighting interpolation tables." );
// build the path names of the lookup tables
SGPath path( globals->get_fg_root() );
// initialize ambient, diffuse and specular tables
SGPath ambient_path = path;
ambient_path.append( "Lighting/ambient" );
_ambient_tbl = std::make_unique<SGInterpTable>( ambient_path );
SGPath diffuse_path = path;
diffuse_path.append( "Lighting/diffuse" );
_diffuse_tbl = std::make_unique<SGInterpTable>( diffuse_path );
SGPath specular_path = path;
specular_path.append( "Lighting/specular" );
_specular_tbl = std::make_unique<SGInterpTable>( specular_path );
// initialize sky table
SGPath sky_path = path;
sky_path.append( "Lighting/sky" );
_sky_tbl = std::make_unique<SGInterpTable>( sky_path );
// update all solar system body positions of interest
globals->get_event_mgr()->addTask("updateObjects",
[this](){ this->updateObjects(); }, 0.5 );
}
void FGLight::reinit () {
_prev_sun_angle = -9999.0;
_dt_total = 0;
_ambient_tbl.reset();
_diffuse_tbl.reset();
_specular_tbl.reset();
_sky_tbl.reset();
init();
updateObjects();
update_sky_color();
update_adj_fog_color();
}
void FGLight::bind () {
SGPropertyNode *prop = globals->get_props();
// Write Only
tie(prop,"/rendering/scene/saturation", SGRawValuePointer<float>(&_saturation));
tie(prop,"/rendering/scene/scattering", SGRawValuePointer<float>(&_scattering));
tie(prop,"/rendering/scene/overcast", SGRawValuePointer<float>(&_overcast));
_sunAngleRad = prop->getNode("/sim/time/sun-angle-rad", true);
_sunAngleRad->setDoubleValue(_sun_angle);
_moonAngleRad = prop->getNode("/sim/time/moon-angle-rad", true);
_moonAngleRad->setDoubleValue(_moon_angle);
_humidity = fgGetNode("/environment/relative-humidity", true);
// Read Only
tie(prop,"/rendering/scene/ambient/red", SGRawValuePointer<float>(&_scene_ambient[0]));
tie(prop,"/rendering/scene/ambient/green", SGRawValuePointer<float>(&_scene_ambient[1]));
tie(prop,"/rendering/scene/ambient/blue", SGRawValuePointer<float>(&_scene_ambient[2]));
tie(prop,"/rendering/scene/diffuse/red", SGRawValuePointer<float>(&_scene_diffuse[0]));
tie(prop,"/rendering/scene/diffuse/green", SGRawValuePointer<float>(&_scene_diffuse[1]));
tie(prop,"/rendering/scene/diffuse/blue", SGRawValuePointer<float>(&_scene_diffuse[2]));
tie(prop,"/rendering/scene/specular/red", SGRawValuePointer<float>(&_scene_specular[0]));
tie(prop,"/rendering/scene/specular/green", SGRawValuePointer<float>(&_scene_specular[1]));
tie(prop,"/rendering/scene/specular/blue", SGRawValuePointer<float>(&_scene_specular[2]));
tie(prop,"/rendering/dome/sun/red", SGRawValuePointer<float>(&_sun_color[0]));
tie(prop,"/rendering/dome/sun/green", SGRawValuePointer<float>(&_sun_color[1]));
tie(prop,"/rendering/dome/sun/blue", SGRawValuePointer<float>(&_sun_color[2]));
tie(prop,"/rendering/dome/sky/red", SGRawValuePointer<float>(&_sky_color[0]));
tie(prop,"/rendering/dome/sky/green", SGRawValuePointer<float>(&_sky_color[1]));
tie(prop,"/rendering/dome/sky/blue", SGRawValuePointer<float>(&_sky_color[2]));
tie(prop,"/rendering/dome/cloud/red", SGRawValuePointer<float>(&_cloud_color[0]));
tie(prop,"/rendering/dome/cloud/green", SGRawValuePointer<float>(&_cloud_color[1]));
tie(prop,"/rendering/dome/cloud/blue", SGRawValuePointer<float>(&_cloud_color[2]));
tie(prop,"/rendering/dome/fog/red", SGRawValuePointer<float>(&_fog_color[0]));
tie(prop,"/rendering/dome/fog/green", SGRawValuePointer<float>(&_fog_color[1]));
tie(prop,"/rendering/dome/fog/blue", SGRawValuePointer<float>(&_fog_color[2]));
// Sun vector
tie(prop,"/ephemeris/sun/local/x", SGRawValuePointer<float>(&_sun_vec[0]));
tie(prop,"/ephemeris/sun/local/y", SGRawValuePointer<float>(&_sun_vec[1]));
tie(prop,"/ephemeris/sun/local/z", SGRawValuePointer<float>(&_sun_vec[2]));
// Moon vector
tie(prop,"/ephemeris/moon/local/x", SGRawValuePointer<float>(&_moon_vec[0]));
tie(prop,"/ephemeris/moon/local/y", SGRawValuePointer<float>(&_moon_vec[1]));
tie(prop,"/ephemeris/moon/local/z", SGRawValuePointer<float>(&_moon_vec[2]));
// Properties used directly by effects
_chromeProps[0] = prop->getNode("/rendering/scene/chrome-light/red", true);
_chromeProps[1] = prop->getNode("/rendering/scene/chrome-light/green",
true);
_chromeProps[2] = prop->getNode("/rendering/scene/chrome-light/blue", true);
_chromeProps[3] = prop->getNode("/rendering/scene/chrome-light/alpha",
true);
for (int i = 0; i < 4; ++i)
_chromeProps[i]->setValue(0.0);
}
void FGLight::unbind () {
_tiedProperties.Untie();
for (int i = 0; i < 4; ++i)
_chromeProps[i] = SGPropertyNode_ptr();
_sunAngleRad = SGPropertyNode_ptr();
_moonAngleRad.reset();
_humidity = SGPropertyNode_ptr();
}
// update lighting parameters based on current sun position
void FGLight::update( double dt )
{
update_adj_fog_color();
if (_prev_sun_angle != _sun_angle) {
_prev_sun_angle = _sun_angle;
update_sky_color();
}
}
void FGLight::update_sky_color () {
const SGVec4f base_sky_color( 0.31, 0.43, 0.69, 1.0 );
const SGVec4f base_fog_color( 0.63, 0.72, 0.88, 1.0 );
// calculate lighting parameters based on sun's relative angle to
// local up
float av = _humidity->getFloatValue() * 45;
float visibility_log = log(av)/11.0;
float visibility_inv = (45000.0 - av)/45000.0;
float deg = _sun_angle * SGD_RADIANS_TO_DEGREES;
if (_saturation < 0.0) _saturation = 0.0;
else if (_saturation > 1.0) _saturation = 1.0;
if (_scattering < 0.0) _scattering = 0.0;
else if (_scattering > 1.0) _scattering = 1.0;
if (_overcast < 0.0) _overcast = 0.0;
else if (_overcast > 1.0) _overcast = 1.0;
float ambient = _ambient_tbl->interpolate( deg ) + visibility_inv/10;
float diffuse = _diffuse_tbl->interpolate( deg );
float specular = _specular_tbl->interpolate( deg ) * visibility_log;
float sky_brightness = _sky_tbl->interpolate( deg );
ambient *= _saturation;
diffuse *= _saturation;
specular *= _saturation;
sky_brightness *= _saturation;
// sky_brightness = 0.15; // used to force a dark sky (when testing)
/** fog color */
float sqr_sky_brightness = sky_brightness * sky_brightness * _scattering;
_fog_color = base_fog_color * sqr_sky_brightness;
_fog_color[3] = base_fog_color[3];
gamma_correct_rgb( _fog_color.data() );
/** sky color */
static const SGVec4f one_vec( 1.0f, 1.0f, 1.0f, 1.0f);
SGVec4f overcast_color = (one_vec - base_sky_color) * _overcast;
_sky_color = (base_sky_color + overcast_color) * sky_brightness;
_sky_color[3] = base_sky_color[3];
gamma_correct_rgb( _sky_color.data() );
/** cloud color */
_cloud_color = base_fog_color * sky_brightness;
/** adjust the cloud colors for sunrise/sunset effects (darken them) */
if (_sun_angle > 1.0) {
float sun2 = 1.0 / sqrt(_sun_angle);
_cloud_color *= sun2;
}
_cloud_color[3] = base_fog_color[3];
gamma_correct_rgb( _cloud_color.data() );
/** ambient light */
_scene_ambient = _fog_color * ambient;
_scene_ambient[3] = _fog_color[3];
gamma_correct_rgb( _scene_ambient.data() );
/** diffuse light */
SGSky* thesky = globals->get_renderer()->getSky();
SGVec4f color = thesky->get_scene_color();
_scene_diffuse = color * diffuse;
_scene_diffuse[3] = color[3];
gamma_correct_rgb( _scene_diffuse.data() );
SGVec4f chrome = _scene_ambient * .4f + _scene_diffuse;
chrome[3] = 1.0f;
if (chrome != _scene_chrome) {
_scene_chrome = chrome;
for (int i = 0; i < 4; ++i)
_chromeProps[i]->setValue(static_cast<double>(_scene_chrome[i]));
}
/** specular light */
_sun_color = thesky->get_sun_color();
_scene_specular = _sun_color * specular;
_scene_specular[3] = _sun_color[3];
gamma_correct_rgb( _scene_specular.data() );
}
// calculate fog color adjusted for sunrise/sunset effects
void FGLight::update_adj_fog_color () {
// double pitch = globals->get_current_view()->getPitch_deg()
// * SGD_DEGREES_TO_RADIANS;
// double pitch_offset = globals->get_current_view()-> getPitchOffset_deg()
// * SGD_DEGREES_TO_RADIANS;
double heading = globals->get_current_view()->getHeading_deg()
* SGD_DEGREES_TO_RADIANS;
double heading_offset = globals->get_current_view()->getHeadingOffset_deg()
* SGD_DEGREES_TO_RADIANS;
// set fog color (we'll try to match the sunset color in the
// direction we are looking
// Do some sanity checking ...
if ( _sun_rotation < -2.0 * SGD_2PI || _sun_rotation > 2.0 * SGD_2PI ) {
SG_LOG( SG_EVENT, SG_ALERT, "Sun rotation bad = " << _sun_rotation );
return;
}
if ( heading < -2.0 * SGD_2PI || heading > 2.0 * SGD_2PI ) {
SG_LOG( SG_EVENT, SG_ALERT, "Heading rotation bad = " << heading );
return;
}
if ( heading_offset < -2.0 * SGD_2PI || heading_offset > 2.0 * SGD_2PI ) {
SG_LOG( SG_EVENT, SG_ALERT, "Heading offset bad = " << heading_offset );
return;
}
static float gamma = system_gamma;
// first determine the difference between our view angle and local
// direction to the sun
//double vert_rotation = pitch + pitch_offset;
// revert to unmodified values before using them.
//
SGSky* thesky = globals->get_renderer()->getSky();
SGVec4f color = thesky->get_scene_color();
gamma_restore_rgb( _fog_color.data(), gamma );
gamma_restore_rgb( _sky_color.data(), gamma );
// Calculate the fog color in the direction of the sun for
// sunrise/sunset effects.
//
_sun_color[0] = color[0]*color[0]*color[0];
_sun_color[1] = color[1]*color[1]*color[1];
_sun_color[2] = color[2]*color[2];
// interpolate between the sunrise/sunset color and the color
// at the opposite direction of this effect. Take in account
// the current visibility.
//
float av = thesky->get_visibility();
if (av > 45000) av = 45000;
float avf = 0.87 - (45000 - av) / 83333.33;
float sif = 0.5 - cos(_sun_angle*2)/2;
if (sif < 1e-3)
sif = 1e-3;
// determine horizontal angle between current view direction and sun
// since _sun_rotation is relative to South, and heading is in the local frame
// we need to account for the 180 degrees offset and differing signs
// hence the negation and SGD_PI adjustment.
double hor_rotation = -_sun_rotation - SGD_PI - heading + heading_offset;
if (hor_rotation < 0 )
hor_rotation = fmod(hor_rotation, SGD_2PI) + SGD_2PI;
else
hor_rotation = fmod(hor_rotation, SGD_2PI);
float rf1 = fabs((hor_rotation - SGD_PI) / SGD_PI); // 0.0 .. 1.0
float rf2 = avf * pow(rf1*rf1, 1/sif) * 1.0639 * _saturation * _scattering;
float rf3 = 1.0 - rf2;
gamma = system_gamma * (0.9 - sif*avf);
_adj_fog_color = rf3 * _fog_color + rf2 * _sun_color;
_adj_fog_color[3] = 0;
gamma_correct_rgb( _adj_fog_color.data(), gamma);
// make sure the colors have their original value before they are being
// used by the rest of the program.
//
gamma_correct_rgb( _fog_color.data(), gamma );
gamma_correct_rgb( _sky_color.data(), gamma );
}
// update all solar system bodies of interest
void FGLight::updateObjects()
{
// update the sun position
bool sun_not_moon = true;
updateBodyPos(sun_not_moon, _sun_lon, _sun_lat,
_sun_vec, _sun_vec_inv,
_sun_angle, _sunAngleRad,
_sun_rotation);
// update the moon position
sun_not_moon = false;
updateBodyPos(sun_not_moon, _moon_lon, _moon_gc_lat,
_moon_vec, _moon_vec_inv,
_moon_angle, _moonAngleRad,
_moon_rotation);
}
// update the position of one solar system body
void FGLight::updateBodyPos(bool sun_not_moon, double& lon, double& lat,
SGVec4f& vec, SGVec4f& vec_inv,
double& angle, SGPropertyNode_ptr AngleRad,
double& rotation)
{
SGTime *t = globals->get_time_params();
// returns lon and lat based on GST
fgBodyPositionGST(t->getGst(), lon, lat, sun_not_moon);
// It might seem that gc_lat needs to be converted to geodetic
// latitude here, but it doesn't. The body latitude is the latitude
// of the point on the earth where the up vector has the same
// angle from geocentric Z as the body direction. But geodetic
// latitude is defined as 90 - angle of up vector from Z!
SGVec3d bodypos = SGVec3d::fromGeoc(SGGeoc::fromRadM(lon, lat,
SGGeodesy::EQURAD));
// update the body vector
vec = SGVec4f(toVec3f(normalize(bodypos)), 0);
vec_inv = - vec;
// calculate the body's relative angle to local up
SGQuatd hlOr = SGQuatd::fromLonLat( globals->get_view_position() );
SGVec3d world_up = hlOr.backTransform( -SGVec3d::e3() );
// cout << "nup = " << nup[0] << "," << nup[1] << ","
// << nup[2] << endl;
// cout << "nbody = " << nbody[0] << "," << nbody[1] << ","
// << nbody[2] << endl;
SGVec3d nbody = normalize(bodypos);
SGVec3d nup = normalize(world_up);
angle = acos( dot( nup, nbody ) );
double signedPI = (angle < 0.0) ? -SGD_PI : SGD_PI;
angle = fmod(angle+signedPI, SGD_2PI) - signedPI;
// Get direction to the body in the local frame.
SGVec3d local_vec = hlOr.transform(nbody);
// Angle from South.
// atan2(y,x) returns the angle between the positive X-axis
// and the vector with the origin at 0, going through (x,y)
// Since the local frame coordinates have x-positive pointing Nord and
// y-positive pointing East we need to negate local_vec.x()
// rotation is positive counterclockwise from South (body in the East)
// and negative clockwise from South (body in the West)
rotation = atan2(local_vec.y(), -local_vec.x());
// cout << " Sky needs to rotate = " << rotation << " rads = "
// << rotation * SGD_RADIANS_TO_DEGREES << " degrees." << endl;
AngleRad->setDoubleValue(angle);
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGLight> registrantFGLight(
SGSubsystemMgr::DISPLAY);

205
src/Time/light.hxx Normal file
View File

@@ -0,0 +1,205 @@
// light.hxx -- lighting routines
//
// Written by Curtis Olson, started April 1998.
//
// Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _LIGHT_HXX
#define _LIGHT_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/compiler.h>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <simgear/math/interpolater.hxx>
// Define a structure containing the global lighting parameters
class FGLight : public SGSubsystem
{
private:
/*
* Lighting look up tables (based on sun angle with local horizon)
*/
std::unique_ptr<SGInterpTable> _ambient_tbl, _diffuse_tbl, _specular_tbl;
std::unique_ptr<SGInterpTable> _sky_tbl;
/**
* position of the sun and moon in various forms
*/
// in geocentric coordinates
double _sun_lon = 0.0, _sun_lat = 0.0;
double _moon_lon = 0.0, _moon_gc_lat = 0.0;
// (in view coordinates)
SGVec4f _sun_vec = {0, 0, 0, 0};
SGVec4f _moon_vec = {0, 0, 0, 0};
// inverse (in view coordinates)
SGVec4f _sun_vec_inv = {0, 0, 0, 0};
SGVec4f _moon_vec_inv = {0, 0, 0, 0};
// the angle between the celestial object and the local horizontal
// (in radians)
double _sun_angle = 0.0 , _moon_angle = 0.0;
double _prev_sun_angle = 0.0;
// the rotation around our vertical axis of the sun (relative to
// due south with positive numbers going in the counter clockwise
// direction.) This is the direction we'd need to face if we
// wanted to travel towards celestial object.
double _sun_rotation = 0.0, _moon_rotation = 0.0;
/**
* Derived lighting values
*/
// ambient, diffuse and specular component
SGVec4f _scene_ambient = {0, 0, 0, 0};
SGVec4f _scene_diffuse = {0, 0, 0, 0};
SGVec4f _scene_specular = {0, 0, 0, 0};
SGVec4f _scene_chrome = {0, 0, 0, 0};
// clear sky, fog and cloud color
SGVec4f _sun_color = {1, 1, 1, 0};
SGVec4f _sky_color = {0, 0, 0, 0};
SGVec4f _fog_color = {0, 0, 0, 0};
SGVec4f _cloud_color = {0, 0, 0, 0};
// clear sky and fog color adjusted for sunset effects
SGVec4f _adj_fog_color = {0, 0, 0, 0};
SGVec4f _adj_sky_color = {0, 0, 0, 0};
// input parameters affected by the weather system
float _saturation = 1.0f;
float _scattering = 0.8f;
float _overcast = 0.0f;
double _dt_total = 0.0;
void update_sky_color ();
void update_adj_fog_color ();
// update all solar system bodies of interest
void updateObjects();
// update the position of one solar system body
void updateBodyPos(bool sun_not_moon, double& lon, double& lat,
SGVec4f& vec, SGVec4f& vec_inv,
double& angle, SGPropertyNode_ptr AngleRad,
double& rotation);
// properties for chrome light; not a tie because I want to fire
// property listeners when the values change.
SGPropertyNode_ptr _chromeProps[4];
SGPropertyNode_ptr _sunAngleRad;
SGPropertyNode_ptr _moonAngleRad;
SGPropertyNode_ptr _humidity;
simgear::TiedPropertyList _tiedProperties;
/**
* Tied-properties helper, record nodes which are tied for easy un-tie-ing
*/
template <typename T>
void tie(SGPropertyNode* aNode, const char* aRelPath, const SGRawValue<T>& aRawValue)
{
_tiedProperties.Tie(aNode->getNode(aRelPath, true), aRawValue);
}
public:
FGLight () = default;
virtual ~FGLight () = default;
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "lighting"; }
// Color related functions
inline const SGVec4f& scene_ambient () const { return _scene_ambient; }
inline const SGVec4f& scene_diffuse () const { return _scene_diffuse; }
inline const SGVec4f& scene_specular () const { return _scene_specular; }
inline const SGVec4f& scene_chrome () const { return _scene_chrome; }
inline const SGVec4f& sky_color () const { return _sky_color; }
inline const SGVec4f& cloud_color () const { return _cloud_color; }
inline const SGVec4f& adj_fog_color () const { return _adj_fog_color; }
inline const SGVec4f& adj_sky_color () const { return _adj_sky_color; }
// Sun related functions
inline double get_sun_angle () const { return _sun_angle; }
inline void set_sun_angle (double a) { _sun_angle = a; }
inline double get_sun_rotation () const { return _sun_rotation; }
inline void set_sun_rotation (double r) { _sun_rotation = r; }
inline double get_sun_lon () const { return _sun_lon; }
inline void set_sun_lon (double l) { _sun_lon = l; }
inline double get_sun_lat () const { return _sun_lat; }
inline void set_sun_lat (double l) { _sun_lat = l; }
inline SGVec4f& sun_vec () { return _sun_vec; }
inline SGVec4f& sun_vec_inv () { return _sun_vec_inv; }
// Moon related functions
inline double get_moon_angle () const { return _moon_angle; }
inline void set_moon_angle (double a) { _moon_angle = a; }
inline double get_moon_rotation () const { return _moon_rotation; }
inline void set_moon_rotation (double r) { _moon_rotation = r; }
inline double get_moon_lon () const { return _moon_lon; }
inline void set_moon_lon (double l) { _moon_lon = l; }
inline double get_moon_gc_lat () const { return _moon_gc_lat; }
inline void set_moon_gc_lat (double l) { _moon_gc_lat = l; }
inline const SGVec4f& moon_vec () const { return _moon_vec; }
inline const SGVec4f& moon_vec_inv () const { return _moon_vec_inv; }
};
#endif // _LIGHT_HXX

89
src/Time/tide.cxx Normal file
View File

@@ -0,0 +1,89 @@
// tide.cxx -- interface for tidal movement
//
// Written by Erik Hofman, Octover 2020
//
// Copyright (C) 2020 Erik Hofman <erik@ehofman.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear/constants.h>
#include <simgear/timing/sg_time.hxx>
#include <simgear/structure/SGExpression.hxx>
#include <Main/globals.hxx>
#include "tide.hxx"
#include "light.hxx"
#include "bodysolver.hxx"
void FGTide::reinit() {
_prev_moon_lon = -9999.0;
}
void FGTide::bind()
{
SGPropertyNode *props = globals->get_props();
viewLon = props->getNode("sim/current-view/viewer-lon-deg", true);
viewLat = props->getNode("sim/current-view/viewer-lat-deg", true);
_tideAnimation = props->getNode("/environment/sea/surface/delta-T-tide", true);
_tideLevelNorm = props->getNode("/sim/time/tide-level-norm", true);
_tideLevelNorm->setDoubleValue(_tide_level);
}
void FGTide::unbind()
{
viewLon.reset();
viewLat.reset();
_tideLevelNorm.reset();
_tideAnimation.reset();
}
#include <Main/fg_props.hxx>
void FGTide::update(double dt)
{
FGLight *l = static_cast<FGLight*>(globals->get_subsystem("lighting"));
// Don't know where the 60 degrees offset comes from but it matches
// the tides perfectly at EHAL. Something to figure out.
// Eureka: It was the latitude (53.45 degrees north).
// It turns out that the moon is draging the tide with an almost
// perfect 45 degrees 'bow-wave' along the equator. Tests at SMBQ
// (0 degrees latitude) confirmed this finding.
double viewer_lon = (viewLon->getDoubleValue()
+ fabs( viewLat->getDoubleValue() )
) * SGD_DEGREES_TO_RADIANS;
double moon_lon = l->get_moon_lon() - viewer_lon;
if (fabs(_prev_moon_lon - moon_lon) > (SGD_PI/360.0))
{
_prev_moon_lon = moon_lon;
double sun_lon = l->get_sun_lon() - viewer_lon;
_tide_level = cos(2.0*moon_lon);
_tide_level += 0.15*cos(2.0*sun_lon);
if (_tide_level < -1.0) _tide_level = -1.0;
else if (_tide_level > 1.0) _tide_level = 1.0;
_tideLevelNorm->setDoubleValue(_tide_level);
_tideAnimation->setDoubleValue(0.5 - 0.5*_tide_level);
}
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGTide> registrantFGTide;

62
src/Time/tide.hxx Normal file
View File

@@ -0,0 +1,62 @@
// tide.hxx -- interface for tidal movement
//
// Written by Erik Hofman, Octover 2020
//
// Copyright (C) 2020 Erik Hofman <erik@ehofman.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef __FGTIDE_HXX
#define __FGTIDE_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/tiedpropertylist.hxx>
class FGTide : public SGSubsystem
{
public:
FGTide() = default;
virtual ~FGTide() = default;
// Subsystem API.
void bind() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "tides"; }
private:
double _prev_moon_lon = -9999.0;
double _tide_level = 0;
SGPropertyNode_ptr viewLon;
SGPropertyNode_ptr viewLat;
SGPropertyNode_ptr _tideLevelNorm;
SGPropertyNode_ptr _tideAnimation;
};
#endif // __FGTIDE_HXX