first commit
This commit is contained in:
17
src/Time/CMakeLists.txt
Normal file
17
src/Time/CMakeLists.txt
Normal 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
675
src/Time/TimeManager.cxx
Normal 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
142
src/Time/TimeManager.hxx
Normal 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
163
src/Time/bodysolver.cxx
Normal 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
65
src/Time/bodysolver.hxx
Normal 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
430
src/Time/light.cxx
Normal 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
205
src/Time/light.hxx
Normal 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
89
src/Time/tide.cxx
Normal 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
62
src/Time/tide.hxx
Normal 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
|
||||
Reference in New Issue
Block a user