src/Aircraft/: interpolate view properties when replaying.
This might avoid some uneven changes to viewing angle when replay frame rate differs from recording frame rate.
This commit is contained in:
parent
1ffc9fb6bf
commit
dd7cbf56cc
4 changed files with 278 additions and 94 deletions
|
@ -187,7 +187,37 @@ static bool ReadFGReplayData2(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<FGReplayData> ReadFGReplayData(
|
/* Removes items more than <n> away from <it>. <n> can be -ve. */
|
||||||
|
template<typename Container, typename Iterator>
|
||||||
|
static void remove_far_away(Container& container, Iterator it, int n)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
|
||||||
|
if (n > 0)
|
||||||
|
{
|
||||||
|
for (int i=0; i<n; ++i)
|
||||||
|
{
|
||||||
|
if (it == container.end()) return;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
container.erase(it, container.end());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i=0; i<-n-1; ++i)
|
||||||
|
{
|
||||||
|
if (it == container.begin()) return;
|
||||||
|
--it;
|
||||||
|
}
|
||||||
|
container.erase(container.begin(), it);
|
||||||
|
}
|
||||||
|
SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns FGReplayData for frame at specified position in file. Uses
|
||||||
|
continuous.m_in_pos_to_frame as a cache, and trims this cache using
|
||||||
|
remove_far_away(). */
|
||||||
|
static std::shared_ptr<FGReplayData> ReadFGReplayData(
|
||||||
|
Continuous& continuous,
|
||||||
std::ifstream& in,
|
std::ifstream& in,
|
||||||
size_t pos,
|
size_t pos,
|
||||||
SGPropertyNode* config,
|
SGPropertyNode* config,
|
||||||
|
@ -197,7 +227,28 @@ std::unique_ptr<FGReplayData> ReadFGReplayData(
|
||||||
int in_compression
|
int in_compression
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
/* Need to clear any eof bit, otherwise seekg() will not work (which is
|
std::shared_ptr<FGReplayData> ret;
|
||||||
|
auto it = continuous.m_in_pos_to_frame.find(pos);
|
||||||
|
|
||||||
|
if (it != continuous.m_in_pos_to_frame.end())
|
||||||
|
{
|
||||||
|
if (0
|
||||||
|
|| (load_signals && !it->second->load_signals)
|
||||||
|
|| (load_multiplayer && !it->second->load_multiplayer)
|
||||||
|
|| (load_extra_properties && !it->second->load_extra_properties)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
/* This frame is in the continuous.m_in_pos_to_frame cache, but
|
||||||
|
doesn't contain all of the required items, so we need to reload. */
|
||||||
|
continuous.m_in_pos_to_frame.erase(it);
|
||||||
|
it = continuous.m_in_pos_to_frame.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (it == continuous.m_in_pos_to_frame.end())
|
||||||
|
{
|
||||||
|
/* Load FGReplayData at offset <pos>.
|
||||||
|
|
||||||
|
We need to clear any eof bit, otherwise seekg() will not work (which is
|
||||||
pretty unhelpful). E.g. see:
|
pretty unhelpful). E.g. see:
|
||||||
https://stackoverflow.com/questions/16364301/whats-wrong-with-the-ifstream-seekg
|
https://stackoverflow.com/questions/16364301/whats-wrong-with-the-ifstream-seekg
|
||||||
*/
|
*/
|
||||||
|
@ -205,7 +256,7 @@ std::unique_ptr<FGReplayData> ReadFGReplayData(
|
||||||
in.clear();
|
in.clear();
|
||||||
in.seekg(pos);
|
in.seekg(pos);
|
||||||
|
|
||||||
std::unique_ptr<FGReplayData> ret(new FGReplayData);
|
ret.reset(new FGReplayData);
|
||||||
|
|
||||||
readRaw(in, ret->sim_time);
|
readRaw(in, ret->sim_time);
|
||||||
if (!in)
|
if (!in)
|
||||||
|
@ -232,6 +283,28 @@ std::unique_ptr<FGReplayData> ReadFGReplayData(
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
it = continuous.m_in_pos_to_frame.lower_bound(pos);
|
||||||
|
it = continuous.m_in_pos_to_frame.insert(it, std::make_pair(pos, ret));
|
||||||
|
|
||||||
|
/* Delete faraway items. */
|
||||||
|
size_t size_old = continuous.m_in_pos_to_frame.size();
|
||||||
|
int n = 2;
|
||||||
|
size_t size_max = 2*n - 1;
|
||||||
|
remove_far_away(continuous.m_in_pos_to_frame, it, n);
|
||||||
|
remove_far_away(continuous.m_in_pos_to_frame, it, -n);
|
||||||
|
size_t size_new = continuous.m_in_pos_to_frame.size();
|
||||||
|
SG_LOG(SG_GENERAL, SG_DEBUG, ""
|
||||||
|
<< " n=" << size_old
|
||||||
|
<< " size_max=" << size_max
|
||||||
|
<< " size_old=" << size_old
|
||||||
|
<< " size_new=" << size_new
|
||||||
|
);
|
||||||
|
assert(size_new <= size_max);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret = it->second;
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,7 +587,17 @@ SGPropertyNode_ptr continuousWriteHeader(
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool replayContinuousInternal(
|
/* Replays one frame from Continuous recording. <offset> and <offset_old> are
|
||||||
|
offsets in file of frames that are >= and < <time> respectively. <offset_old>
|
||||||
|
may be 0, in which case it is ignored.
|
||||||
|
|
||||||
|
We load the frame(s) from disc, omitting some data depending on
|
||||||
|
replay_signals, replay_multiplayer and replay_extra_properties. Then call
|
||||||
|
m_pRecorder->replay(), which updates the global state.
|
||||||
|
|
||||||
|
Returns true on success, otherwise we failed to read from Continuous recording.
|
||||||
|
*/
|
||||||
|
static bool replayContinuousInternal(
|
||||||
Continuous& continuous,
|
Continuous& continuous,
|
||||||
FGFlightRecorder* recorder,
|
FGFlightRecorder* recorder,
|
||||||
double time,
|
double time,
|
||||||
|
@ -529,7 +612,8 @@ bool replayContinuousInternal(
|
||||||
int* ysize
|
int* ysize
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
std::unique_ptr<FGReplayData> replay_data = ReadFGReplayData(
|
std::shared_ptr<FGReplayData> replay_data = ReadFGReplayData(
|
||||||
|
continuous,
|
||||||
continuous.m_in,
|
continuous.m_in,
|
||||||
offset,
|
offset,
|
||||||
continuous.m_in_config,
|
continuous.m_in_config,
|
||||||
|
@ -544,10 +628,11 @@ bool replayContinuousInternal(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
assert(replay_data.get());
|
assert(replay_data.get());
|
||||||
std::unique_ptr<FGReplayData> replay_data_old;
|
std::shared_ptr<FGReplayData> replay_data_old;
|
||||||
if (offset_old)
|
if (offset_old)
|
||||||
{
|
{
|
||||||
replay_data_old = ReadFGReplayData(
|
replay_data_old = ReadFGReplayData(
|
||||||
|
continuous,
|
||||||
continuous.m_in,
|
continuous.m_in,
|
||||||
offset_old,
|
offset_old,
|
||||||
continuous.m_in_config,
|
continuous.m_in_config,
|
||||||
|
@ -557,11 +642,12 @@ bool replayContinuousInternal(
|
||||||
continuous.m_in_compression
|
continuous.m_in_compression
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_BULK,
|
if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_DEBUG,
|
||||||
"replay():"
|
"replay():"
|
||||||
<< " time=" << time
|
<< " time=" << time
|
||||||
<< " offset=" << offset
|
<< " offset=" << offset
|
||||||
<< " offset_old=" << offset_old
|
<< " offset_old=" << offset_old
|
||||||
|
<< " replay_data_old=" << replay_data_old
|
||||||
<< " replay_data->raw_data.size()=" << replay_data->raw_data.size()
|
<< " replay_data->raw_data.size()=" << replay_data->raw_data.size()
|
||||||
<< " replay_data->multiplayer_messages.size()=" << replay_data->multiplayer_messages.size()
|
<< " replay_data->multiplayer_messages.size()=" << replay_data->multiplayer_messages.size()
|
||||||
<< " replay_data->extra_properties.size()=" << replay_data->extra_properties.size()
|
<< " replay_data->extra_properties.size()=" << replay_data->extra_properties.size()
|
||||||
|
@ -733,12 +819,19 @@ bool replayContinuous(FGReplayInternal& self, double time)
|
||||||
|
|
||||||
if (replay_this_frame)
|
if (replay_this_frame)
|
||||||
{
|
{
|
||||||
|
size_t pos_prev = 0;
|
||||||
|
if (p_before != self.m_continuous->m_in_time_to_frameinfo.begin())
|
||||||
|
{
|
||||||
|
auto p_before_prev = p_before;
|
||||||
|
--p_before_prev;
|
||||||
|
pos_prev = p_before_prev->second.offset;
|
||||||
|
}
|
||||||
bool ok = replayContinuousInternal(
|
bool ok = replayContinuousInternal(
|
||||||
*self.m_continuous,
|
*self.m_continuous,
|
||||||
self.m_flight_recorder.get(),
|
self.m_flight_recorder.get(),
|
||||||
p_before->first,
|
p_before->first,
|
||||||
p_before->second.offset,
|
p_before->second.offset,
|
||||||
0 /*offset_old*/,
|
pos_prev /*offset_old*/,
|
||||||
false /*replay_signals*/,
|
false /*replay_signals*/,
|
||||||
p_before->first > time - multiplayer_recent /*replay_multiplayer*/,
|
p_before->first > time - multiplayer_recent /*replay_multiplayer*/,
|
||||||
true /*replay_extra_properties*/,
|
true /*replay_extra_properties*/,
|
||||||
|
|
|
@ -27,6 +27,8 @@ struct Continuous : SGPropertyChangeListener
|
||||||
SGPropertyNode_ptr m_in_config;
|
SGPropertyNode_ptr m_in_config;
|
||||||
double m_in_time_last = 0;
|
double m_in_time_last = 0;
|
||||||
double m_in_frame_time_last = 0;
|
double m_in_frame_time_last = 0;
|
||||||
|
std::map<size_t, std::shared_ptr<FGReplayData>>
|
||||||
|
m_in_pos_to_frame;
|
||||||
|
|
||||||
std::ifstream m_indexing_in;
|
std::ifstream m_indexing_in;
|
||||||
std::streampos m_indexing_pos;
|
std::streampos m_indexing_pos;
|
||||||
|
@ -48,29 +50,15 @@ struct Continuous : SGPropertyChangeListener
|
||||||
int m_in_compression = 0;
|
int m_in_compression = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Attempts to load Continuous recording header properties into
|
/* Attempts to load Continuous recording header properties into
|
||||||
// <properties>. If in is null we use internal std::fstream, otherwise we
|
<properties>. If in is null we use internal std::fstream, otherwise we use *in.
|
||||||
// use *in.
|
|
||||||
//
|
Returns 0 on success, +1 if we may succeed after further download, or -1 if
|
||||||
// Returns 0 on success, +1 if we may succeed after further download, or -1
|
recording is not a Continuous recording. */
|
||||||
// if recording is not a Continuous recording.
|
|
||||||
//
|
|
||||||
int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
|
int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
|
||||||
|
|
||||||
|
|
||||||
// Reads all or part of a FGReplayData from Continuous file.
|
/* Writes one frame of continuous record information. */
|
||||||
std::unique_ptr<FGReplayData> ReadFGReplayData(
|
|
||||||
std::ifstream& in,
|
|
||||||
size_t pos,
|
|
||||||
SGPropertyNode* config,
|
|
||||||
bool load_signals,
|
|
||||||
bool load_multiplayer,
|
|
||||||
bool load_extra_properties,
|
|
||||||
int in_compression
|
|
||||||
);
|
|
||||||
|
|
||||||
// Writes one frame of continuous record information.
|
|
||||||
//
|
|
||||||
bool continuousWriteFrame(
|
bool continuousWriteFrame(
|
||||||
Continuous& continuous,
|
Continuous& continuous,
|
||||||
FGReplayData* r,
|
FGReplayData* r,
|
||||||
|
@ -78,18 +66,17 @@ bool continuousWriteFrame(
|
||||||
SGPropertyNode_ptr config
|
SGPropertyNode_ptr config
|
||||||
);
|
);
|
||||||
|
|
||||||
// Opens continuous recording file and writes header.
|
/* Opens continuous recording file and writes header.
|
||||||
//
|
|
||||||
// If MetaData is unset, we initialise it by calling saveSetup(). Otherwise
|
If MetaData is unset, we initialise it by calling saveSetup(). Otherwise should
|
||||||
// should be already set up.
|
be already set up.
|
||||||
//
|
|
||||||
// If Config is unset, we make it point to a new node populated by
|
If Config is unset, we make it point to a new node populated by
|
||||||
// m_pRecorder->getConfig(). Otherwise it should be already set up to point to
|
m_pRecorder->getConfig(). Otherwise it should be already set up to point to
|
||||||
// such information.
|
such information.
|
||||||
//
|
|
||||||
// If path_override is not "", we use it as the path (instead of the path
|
If path_override is not "", we use it as the path (instead of the path
|
||||||
// determined by saveSetup().
|
determined by saveSetup(). */
|
||||||
//
|
|
||||||
SGPropertyNode_ptr continuousWriteHeader(
|
SGPropertyNode_ptr continuousWriteHeader(
|
||||||
Continuous& continuous,
|
Continuous& continuous,
|
||||||
FGFlightRecorder* m_pRecorder,
|
FGFlightRecorder* m_pRecorder,
|
||||||
|
@ -98,32 +85,12 @@ SGPropertyNode_ptr continuousWriteHeader(
|
||||||
FGTapeType tape_type
|
FGTapeType tape_type
|
||||||
);
|
);
|
||||||
|
|
||||||
// Replays one frame from Continuous recording. <offset> and <offset_old>
|
/* Replays one frame from Continuous recording.
|
||||||
// are offsets in file of frames that are >= and < <time> respectively.
|
|
||||||
// <offset_old> may be 0, in which case it is ignored.
|
|
||||||
//
|
|
||||||
// We load the frame(s) from disc, omitting some data depending on
|
|
||||||
// replay_signals, replay_multiplayer and replay_extra_properties. Then call
|
|
||||||
// m_pRecorder->replay(), which updates the global state.
|
|
||||||
//
|
|
||||||
// Returns true on success, otherwise we failed to read from Continuous
|
|
||||||
// recording.
|
|
||||||
//
|
|
||||||
bool replayContinuousInternal(
|
|
||||||
Continuous& continuous,
|
|
||||||
FGFlightRecorder* recorder,
|
|
||||||
double time,
|
|
||||||
size_t offset,
|
|
||||||
size_t offset_old,
|
|
||||||
bool replay_signals,
|
|
||||||
bool replay_multiplayer,
|
|
||||||
bool replay_extra_properties,
|
|
||||||
int* xpos,
|
|
||||||
int* ypos,
|
|
||||||
int* xsize,
|
|
||||||
int* ysize
|
|
||||||
);
|
|
||||||
|
|
||||||
|
Returns true on success, otherwise we failed to read from Continuous recording.
|
||||||
|
*/
|
||||||
bool replayContinuous(FGReplayInternal& self, double time);
|
bool replayContinuous(FGReplayInternal& self, double time);
|
||||||
|
|
||||||
|
/* Stops any video recording that was started because of
|
||||||
|
continuous->m_replay_create_video. */
|
||||||
void continuous_replay_video_end(Continuous& continuous);
|
void continuous_replay_video_end(Continuous& continuous);
|
||||||
|
|
|
@ -737,6 +737,83 @@ static void setInt(const std::string& value, int* out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Converts string to double, writing to out-para <out> and setting <ok> to
|
||||||
|
true. If conversion fails or fails to use the entire input string, sets ok to
|
||||||
|
false. */
|
||||||
|
static void string_to_double(const std::string& s, double& out, bool& ok)
|
||||||
|
{
|
||||||
|
errno = 0;
|
||||||
|
char* end;
|
||||||
|
out = strtod(s.c_str(), &end);
|
||||||
|
if (errno || !end || *end != 0)
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Updates property <path> to <value_next_string>. If property is also in
|
||||||
|
<frame_prev> and values look like floating point, we interpolate using <ratio>.
|
||||||
|
*/
|
||||||
|
static void replayProperty(
|
||||||
|
const std::string& path,
|
||||||
|
const std::string& value_next_string,
|
||||||
|
const FGReplayData* frame_prev,
|
||||||
|
double ratio
|
||||||
|
)
|
||||||
|
{
|
||||||
|
SGPropertyNode* p = globals->get_props()->getNode(path, true /*create*/);
|
||||||
|
bool done = false;
|
||||||
|
if (frame_prev)
|
||||||
|
{
|
||||||
|
/* Check whether <path> is in frame_prev's list of property changes. */
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "p && _pLastBuffer");
|
||||||
|
auto p_prev_it = frame_prev->replay_extra_property_changes.find(path);
|
||||||
|
if (p_prev_it != frame_prev->replay_extra_property_changes.end())
|
||||||
|
{
|
||||||
|
/* Property <path> is also in frame_prev. */
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property in frame_prev and frame_next:"
|
||||||
|
<< " " << path);
|
||||||
|
const std::string& value_prev_string = p_prev_it->second;
|
||||||
|
bool value_prev_ok;
|
||||||
|
bool value_next_ok;
|
||||||
|
double value_prev;
|
||||||
|
double value_next;
|
||||||
|
string_to_double(value_prev_string, value_prev, value_prev_ok);
|
||||||
|
string_to_double(value_next_string, value_next, value_next_ok);
|
||||||
|
if (value_prev_ok && value_next_ok)
|
||||||
|
{
|
||||||
|
/* Both values look like floating point so we interpolate. */
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property is fp");
|
||||||
|
TInterpolation interpolation = TInterpolation::linear;
|
||||||
|
if (simgear::strutils::ends_with(path, "-deg"))
|
||||||
|
{
|
||||||
|
interpolation = TInterpolation::angular_deg;
|
||||||
|
}
|
||||||
|
else if (simgear::strutils::ends_with(path, "-rad"))
|
||||||
|
{
|
||||||
|
interpolation = TInterpolation::angular_rad;
|
||||||
|
}
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "calling weighting() ratio=" << ratio);
|
||||||
|
double value_interpolated = weighting(
|
||||||
|
interpolation,
|
||||||
|
ratio,
|
||||||
|
value_prev,
|
||||||
|
value_next
|
||||||
|
);
|
||||||
|
SG_LOG(SG_GENERAL, SG_DEBUG, "Interpolating " << path
|
||||||
|
<< ": [" << value_prev << " .. " << value_next
|
||||||
|
<< "] => " << value_interpolated
|
||||||
|
);
|
||||||
|
globals->get_props()->setDoubleValue(path, value_interpolated);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!done) p->setStringValue(path, value_next_string);
|
||||||
|
}
|
||||||
|
|
||||||
/** Replay.
|
/** Replay.
|
||||||
* Restore all properties with data from given buffer. */
|
* Restore all properties with data from given buffer. */
|
||||||
void
|
void
|
||||||
|
@ -750,11 +827,10 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
|
||||||
{
|
{
|
||||||
const char* pLastBuffer = (_pLastBuffer && !_pLastBuffer->raw_data.empty()) ? &_pLastBuffer->raw_data.front() : nullptr;
|
const char* pLastBuffer = (_pLastBuffer && !_pLastBuffer->raw_data.empty()) ? &_pLastBuffer->raw_data.front() : nullptr;
|
||||||
const char* pBuffer = (_pNextBuffer && !_pNextBuffer->raw_data.empty()) ? &_pNextBuffer->raw_data.front() : nullptr;
|
const char* pBuffer = (_pNextBuffer && !_pNextBuffer->raw_data.empty()) ? &_pNextBuffer->raw_data.front() : nullptr;
|
||||||
|
double ratio = 1.0;
|
||||||
if (pBuffer) {
|
if (pBuffer) {
|
||||||
/* Replay signals. */
|
/* Replay signals. */
|
||||||
int Offset = 0;
|
int Offset = 0;
|
||||||
double ratio = 1.0;
|
|
||||||
if (pLastBuffer)
|
if (pLastBuffer)
|
||||||
{
|
{
|
||||||
double NextSimTime = _pNextBuffer->sim_time;
|
double NextSimTime = _pNextBuffer->sim_time;
|
||||||
|
@ -953,13 +1029,57 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
|
||||||
if (replay_main_view) {
|
if (replay_main_view) {
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "SimTime=" << SimTime
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "SimTime=" << SimTime
|
||||||
<< " replaying view change: " << path << "=" << value);
|
<< " replaying view change: " << path << "=" << value);
|
||||||
globals->get_props()->setStringValue(path, value);
|
/* Interpolate floating point values if possible. */
|
||||||
|
replayProperty(path, value, _pLastBuffer, ratio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (replay_extra_properties) {
|
else if (replay_extra_properties) {
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "SimTime=" << SimTime
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "SimTime=" << SimTime
|
||||||
<< " replaying extra_property change: " << path << "=" << value);
|
<< " replaying extra_property change: " << path << "=" << value);
|
||||||
globals->get_props()->setStringValue(path, value);
|
SGPropertyNode* p = globals->get_props()->getNode(path, true /*create*/);
|
||||||
|
bool done = false;
|
||||||
|
if (p && _pLastBuffer) {
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "p && _pLastBuffer");
|
||||||
|
auto p_prev_it = _pLastBuffer->replay_extra_property_changes.find(path);
|
||||||
|
if (p_prev_it != _pLastBuffer->replay_extra_property_changes.end()) {
|
||||||
|
/* Property <path> is in both _pLastBuffer and
|
||||||
|
_pNextBuffer, so if it is floating point, we will
|
||||||
|
interpolate. */
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property in _pLastBuffer and _pNextBuffer:"
|
||||||
|
<< " " << path);
|
||||||
|
const std::string& valus_prev = p_prev_it->second;
|
||||||
|
size_t value_prev_len;
|
||||||
|
size_t value_next_len;
|
||||||
|
double value_prev = std::stod(valus_prev, &value_prev_len);
|
||||||
|
double value_next = std::stod(value, &value_next_len);
|
||||||
|
if (value_prev_len == valus_prev.size() && value_next_len == value.size()) {
|
||||||
|
/* Both values look like floating point so we will
|
||||||
|
interpolate. */
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property is fp");
|
||||||
|
TInterpolation interpolation = TInterpolation::linear;
|
||||||
|
if (simgear::strutils::ends_with(path, "-deg")) {
|
||||||
|
interpolation = TInterpolation::angular_deg;
|
||||||
|
}
|
||||||
|
else if (simgear::strutils::ends_with(path, "-rad")) {
|
||||||
|
interpolation = TInterpolation::angular_rad;
|
||||||
|
}
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "calling weighting()");
|
||||||
|
double value_interpolated = weighting(
|
||||||
|
interpolation,
|
||||||
|
ratio,
|
||||||
|
value_prev,
|
||||||
|
value_next
|
||||||
|
);
|
||||||
|
SG_LOG(SG_GENERAL, SG_DEBUG, "Interpolating " << path
|
||||||
|
<< ": [" << value_prev << " .. " << value_next
|
||||||
|
<< "] => " << value_interpolated
|
||||||
|
);
|
||||||
|
globals->get_props()->setDoubleValue(path, value_interpolated);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!done) p->setStringValue(path, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,10 @@ extern const char* const FlightRecorderFileMagic;
|
||||||
/* Data for a single frame. */
|
/* Data for a single frame. */
|
||||||
struct FGReplayData
|
struct FGReplayData
|
||||||
{
|
{
|
||||||
|
bool load_signals;
|
||||||
|
bool load_multiplayer;
|
||||||
|
bool load_extra_properties;
|
||||||
|
|
||||||
double sim_time;
|
double sim_time;
|
||||||
|
|
||||||
// Our aircraft state.
|
// Our aircraft state.
|
||||||
|
|
Loading…
Add table
Reference in a new issue