diff --git a/src/Aircraft/CMakeLists.txt b/src/Aircraft/CMakeLists.txt index 3dcd385c3..5c379f014 100644 --- a/src/Aircraft/CMakeLists.txt +++ b/src/Aircraft/CMakeLists.txt @@ -4,12 +4,14 @@ set(SOURCES controls.cxx replay.cxx flightrecorder.cxx + FlightHistory.cxx ) set(HEADERS controls.hxx replay.hxx flightrecorder.hxx + FlightHistory.hxx ) diff --git a/src/Aircraft/FlightHistory.cxx b/src/Aircraft/FlightHistory.cxx new file mode 100644 index 000000000..410d4e582 --- /dev/null +++ b/src/Aircraft/FlightHistory.cxx @@ -0,0 +1,166 @@ +// FlightHistory +// +// Written by James Turner, started December 2012. +// +// Copyright (C) 2012 James Turner - zakalawe (at) mac 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 St, Fifth Floor, Boston, MA 02110-1301, USA. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "FlightHistory.hxx" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include
+#include
+ +FGFlightHistory::FGFlightHistory() : + m_sampleInterval(5.0), + m_validSampleCount(SAMPLE_BUCKET_WIDTH) +{ +} + +FGFlightHistory::~FGFlightHistory() +{ +} + +void FGFlightHistory::init() +{ + m_sampleInterval = fgGetDouble("/sim/history/sample-interval-sec", 1.0); + if (m_sampleInterval <= 0.0) { // would be bad + SG_LOG(SG_FLIGHT, SG_INFO, "invalid flight-history sample interval:" << m_sampleInterval + << ", defaulting to " << m_sampleInterval); + m_sampleInterval = 1.0; + } + + m_weightOnWheels = NULL; +// reset the history when we detect a take-off + if (fgGetBool("/sim/history/clear-on-takeoff", true)) { + m_weightOnWheels = fgGetNode("/gear/gear[1]/wow", 0, true); + m_lastWoW = m_weightOnWheels->getBoolValue(); + } + + // force bucket re-allocation + m_validSampleCount = SAMPLE_BUCKET_WIDTH; +} + +void FGFlightHistory::shutdown() +{ + clear(); +} + +void FGFlightHistory::reinit() +{ + shutdown(); + init(); +} + +void FGFlightHistory::update(double dt) +{ + SG_UNUSED(dt); // we care about sim-time, not frame-time + + if (m_weightOnWheels) { + + if (m_lastWoW && !m_weightOnWheels->getBoolValue()) { + SG_LOG(SG_FLIGHT, SG_INFO, "history: detected main-gear takeoff, clearing history"); + clear(); + } + } // of rest-on-takeoff enabled + + double elapsed = globals->get_sim_time_sec() - m_lastCaptureTime; + if (elapsed > m_sampleInterval) { + capture(); + } +} + +void FGFlightHistory::allocateNewBucket() +{ + SampleBucket* bucket = new SampleBucket; + m_buckets.push_back(bucket); + m_validSampleCount = 0; +} + +void FGFlightHistory::capture() +{ + if (m_validSampleCount == SAMPLE_BUCKET_WIDTH) { + // bucket is full, allocate a new one + allocateNewBucket(); + } + + m_lastCaptureTime = globals->get_sim_time_sec(); + Sample* sample = m_buckets.back()->samples + m_validSampleCount; + + sample->simTimeMSec = static_cast(m_lastCaptureTime * 1000.0); + sample->position = globals->get_aircraft_position(); + + double heading, pitch, roll; + globals->get_aircraft_orientation(heading, pitch, roll); + sample->heading = static_cast(heading); + sample->pitch = static_cast(pitch); + sample->roll = static_cast(roll); + + ++m_validSampleCount; +} + +SGGeodVec FGFlightHistory::pathForHistory(double minEdgeLengthM) const +{ + SGGeodVec result; + if (m_buckets.empty()) { + return result; + } + + result.push_back(m_buckets.front()->samples[0].position); + SGVec3d lastOutputCart = SGVec3d::fromGeod(result.back()); + double minLengthSqr = minEdgeLengthM * minEdgeLengthM; + + BOOST_FOREACH(SampleBucket* bucket, m_buckets) { + unsigned int count = (bucket == m_buckets.back() ? m_validSampleCount : SAMPLE_BUCKET_WIDTH); + + // iterate over all the valid samples in the bucket + for (unsigned int index = 0; index < count; ++index) { + SGGeod g = bucket->samples[index].position; + SGVec3d cart(SGVec3d::fromGeod(g)); + if (distSqr(cart, lastOutputCart) > minLengthSqr) { + lastOutputCart = cart; + result.push_back(g); + } + } // of samples iteration + } // of buckets iteration + + return result; +} + +void FGFlightHistory::clear() +{ + BOOST_FOREACH(SampleBucket* ptr, m_buckets) { + delete ptr; + } + m_buckets.clear(); + m_validSampleCount = SAMPLE_BUCKET_WIDTH; +} + diff --git a/src/Aircraft/FlightHistory.hxx b/src/Aircraft/FlightHistory.hxx new file mode 100644 index 000000000..29b0fb122 --- /dev/null +++ b/src/Aircraft/FlightHistory.hxx @@ -0,0 +1,105 @@ +// FlightHistory +// +// Written by James Turner, started December 2012. +// +// Copyright (C) 2012 James Turner - zakalawe (at) mac 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 St, Fifth Floor, Boston, MA 02110-1301, USA. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef FG_AIRCRAFT_FLIGHT_HISTORY_HXX +#define FG_AIRCRAFT_FLIGHT_HISTORY_HXX + +#include +#include +#include + +#include +#include // for std::auto_ptr + +typedef std::vector SGGeodVec; + +/** + * record the history of the aircraft's movements, making it available + * as a contiguous block. This can be used to show the historical flight-path + * over a long period of time (unlike the replay system), but only a small, + * fixed set of properties are recorded. (Positioned and orientation, but + * not velocity, acceleration, control inputs, or so on) + */ +class FGFlightHistory : public SGSubsystem +{ +public: + FGFlightHistory(); + virtual ~FGFlightHistory(); + + virtual void init(); + virtual void shutdown(); + virtual void reinit(); + virtual void update(double dt); + + /** + * retrieve the path, collapsing segments shorter than + * the specified minimum length + */ + SGGeodVec pathForHistory(double minEdgeLengthM = 50.0) const; +private: + /** + * @class A single data sample in the history system. + */ + class Sample + { + public: + SGGeod position; + /// heading, pitch and roll can be recorded at lower precision + /// than a double - actually 16 bits might be sufficient + float heading, pitch, roll; + int simTimeMSec; + }; + + static const int SAMPLE_BUCKET_WIDTH = 1024; + + /** + * Bucket is a fixed-size container of samples. This is a crude slab + * allocation of samples, in chunks defined by the width constant above. + * Keep in mind that even with a 1Hz sample frequency, we use less than + * 200kbytes per hour - avoiding continous malloc traffic, or expensive + * std::vector reallocations, is the key factor here. + */ + class SampleBucket + { + public: + Sample samples[SAMPLE_BUCKET_WIDTH]; + }; + + double m_lastCaptureTime; + double m_sampleInterval; ///< sample interval in seconds +/// our store of samples (in buckets). The last bucket is partially full, +/// with the number of valid samples indicated by m_validSampleCount + std::vector m_buckets; + +/// number of valid samples in the final bucket + unsigned int m_validSampleCount; + + SGPropertyNode_ptr m_weightOnWheels; + bool m_lastWoW; + + void allocateNewBucket(); + + void clear(); + void capture(); +}; + +#endif \ No newline at end of file diff --git a/src/GUI/MapWidget.cxx b/src/GUI/MapWidget.cxx index e92e7106d..41525cde2 100644 --- a/src/GUI/MapWidget.cxx +++ b/src/GUI/MapWidget.cxx @@ -25,6 +25,7 @@ #include #include
// fgGetKeyModifiers() #include +#include const char* RULER_LEGEND_KEY = "ruler-legend"; @@ -411,6 +412,7 @@ void MapWidget::setProperty(SGPropertyNode_ptr prop) _root->setIntValue("max-zoom", MAX_ZOOM); _root->setBoolValue("centre-on-aircraft", true); _root->setBoolValue("draw-data", false); + _root->setBoolValue("draw-flight-history", false); _root->setBoolValue("magnetic-headings", true); } @@ -611,6 +613,7 @@ void MapWidget::draw(int dx, int dy) drawNavRadio(fgGetNode("/instrumentation/nav[0]", false)); drawNavRadio(fgGetNode("/instrumentation/nav[1]", false)); paintAircraftLocation(_aircraft); + drawFlightHistory(); paintRoute(); paintRuler(); @@ -748,6 +751,28 @@ void MapWidget::paintRoute() } // of second waypoint iteration } +void MapWidget::drawFlightHistory() +{ + FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history"); + if (!history || !_root->getBoolValue("draw-flight-history")) { + return; + } + + // first pass, draw the actual lines + glLineWidth(2.0); + + SGGeodVec gv(history->pathForHistory()); + glColor4f(0.0, 0.0, 1.0, 0.7); + + glBegin(GL_LINE_STRIP); + for (unsigned int i=0; i #include +#include #include #include #include @@ -711,7 +712,8 @@ void fgCreateSubsystems() { // Initialize the replay subsystem //////////////////////////////////////////////////////////////////// globals->add_subsystem("replay", new FGReplay); - + globals->add_subsystem("history", new FGFlightHistory); + #ifdef ENABLE_AUDIO_SUPPORT //////////////////////////////////////////////////////////////////// // Initialize the sound-effects subsystem.