1
0
Fork 0

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:
Julian Smith 2021-12-06 00:23:49 +00:00
parent 1ffc9fb6bf
commit dd7cbf56cc
4 changed files with 278 additions and 94 deletions

View file

@ -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*/,

View file

@ -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);

View file

@ -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);
} }
} }
} }

View file

@ -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.