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;
|
||||
}
|
||||
|
||||
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,
|
||||
size_t pos,
|
||||
SGPropertyNode* config,
|
||||
|
@ -197,40 +227,83 @@ std::unique_ptr<FGReplayData> ReadFGReplayData(
|
|||
int in_compression
|
||||
)
|
||||
{
|
||||
/* Need to clear any eof bit, otherwise seekg() will not work (which is
|
||||
pretty unhelpful). E.g. see:
|
||||
https://stackoverflow.com/questions/16364301/whats-wrong-with-the-ifstream-seekg
|
||||
*/
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "reading frame. pos=" << pos);
|
||||
in.clear();
|
||||
in.seekg(pos);
|
||||
std::shared_ptr<FGReplayData> ret;
|
||||
auto it = continuous.m_in_pos_to_frame.find(pos);
|
||||
|
||||
std::unique_ptr<FGReplayData> ret(new FGReplayData);
|
||||
|
||||
readRaw(in, ret->sim_time);
|
||||
if (!in)
|
||||
if (it != continuous.m_in_pos_to_frame.end())
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||
return nullptr;
|
||||
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();
|
||||
}
|
||||
}
|
||||
bool ok;
|
||||
if (in_compression)
|
||||
if (it == continuous.m_in_pos_to_frame.end())
|
||||
{
|
||||
uint8_t flags;
|
||||
uint32_t compressed_size;
|
||||
in.read((char*) &flags, sizeof(flags));
|
||||
in.read((char*) &compressed_size, sizeof(compressed_size));
|
||||
simgear::ZlibDecompressorIStream in_decompress(in, SGPath(), simgear::ZLibCompressionFormat::ZLIB_RAW);
|
||||
ok = ReadFGReplayData2(in_decompress, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
|
||||
/* Load FGReplayData at offset <pos>.
|
||||
|
||||
We need to clear any eof bit, otherwise seekg() will not work (which is
|
||||
pretty unhelpful). E.g. see:
|
||||
https://stackoverflow.com/questions/16364301/whats-wrong-with-the-ifstream-seekg
|
||||
*/
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "reading frame. pos=" << pos);
|
||||
in.clear();
|
||||
in.seekg(pos);
|
||||
|
||||
ret.reset(new FGReplayData);
|
||||
|
||||
readRaw(in, ret->sim_time);
|
||||
if (!in)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||
return nullptr;
|
||||
}
|
||||
bool ok;
|
||||
if (in_compression)
|
||||
{
|
||||
uint8_t flags;
|
||||
uint32_t compressed_size;
|
||||
in.read((char*) &flags, sizeof(flags));
|
||||
in.read((char*) &compressed_size, sizeof(compressed_size));
|
||||
simgear::ZlibDecompressorIStream in_decompress(in, SGPath(), simgear::ZLibCompressionFormat::ZLIB_RAW);
|
||||
ok = ReadFGReplayData2(in_decompress, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = ReadFGReplayData2(in, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
|
||||
}
|
||||
if (!ok)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||
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
|
||||
{
|
||||
ok = ReadFGReplayData2(in, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
|
||||
}
|
||||
if (!ok)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||
return nullptr;
|
||||
ret = it->second;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -514,7 +587,17 @@ SGPropertyNode_ptr continuousWriteHeader(
|
|||
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,
|
||||
FGFlightRecorder* recorder,
|
||||
double time,
|
||||
|
@ -529,7 +612,8 @@ bool replayContinuousInternal(
|
|||
int* ysize
|
||||
)
|
||||
{
|
||||
std::unique_ptr<FGReplayData> replay_data = ReadFGReplayData(
|
||||
std::shared_ptr<FGReplayData> replay_data = ReadFGReplayData(
|
||||
continuous,
|
||||
continuous.m_in,
|
||||
offset,
|
||||
continuous.m_in_config,
|
||||
|
@ -544,10 +628,11 @@ bool replayContinuousInternal(
|
|||
return false;
|
||||
}
|
||||
assert(replay_data.get());
|
||||
std::unique_ptr<FGReplayData> replay_data_old;
|
||||
std::shared_ptr<FGReplayData> replay_data_old;
|
||||
if (offset_old)
|
||||
{
|
||||
replay_data_old = ReadFGReplayData(
|
||||
continuous,
|
||||
continuous.m_in,
|
||||
offset_old,
|
||||
continuous.m_in_config,
|
||||
|
@ -557,11 +642,12 @@ bool replayContinuousInternal(
|
|||
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():"
|
||||
<< " time=" << time
|
||||
<< " offset=" << offset
|
||||
<< " offset_old=" << offset_old
|
||||
<< " replay_data_old=" << replay_data_old
|
||||
<< " replay_data->raw_data.size()=" << replay_data->raw_data.size()
|
||||
<< " replay_data->multiplayer_messages.size()=" << replay_data->multiplayer_messages.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)
|
||||
{
|
||||
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(
|
||||
*self.m_continuous,
|
||||
self.m_flight_recorder.get(),
|
||||
p_before->first,
|
||||
p_before->second.offset,
|
||||
0 /*offset_old*/,
|
||||
pos_prev /*offset_old*/,
|
||||
false /*replay_signals*/,
|
||||
p_before->first > time - multiplayer_recent /*replay_multiplayer*/,
|
||||
true /*replay_extra_properties*/,
|
||||
|
|
|
@ -27,6 +27,8 @@ struct Continuous : SGPropertyChangeListener
|
|||
SGPropertyNode_ptr m_in_config;
|
||||
double m_in_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::streampos m_indexing_pos;
|
||||
|
@ -48,29 +50,15 @@ struct Continuous : SGPropertyChangeListener
|
|||
int m_in_compression = 0;
|
||||
};
|
||||
|
||||
// Attempts to load Continuous recording header properties into
|
||||
// <properties>. If in is null we use internal std::fstream, otherwise we
|
||||
// use *in.
|
||||
//
|
||||
// Returns 0 on success, +1 if we may succeed after further download, or -1
|
||||
// if recording is not a Continuous recording.
|
||||
//
|
||||
/* Attempts to load Continuous recording header properties into
|
||||
<properties>. If in is null we use internal std::fstream, otherwise we use *in.
|
||||
|
||||
Returns 0 on success, +1 if we may succeed after further download, or -1 if
|
||||
recording is not a Continuous recording. */
|
||||
int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
|
||||
|
||||
|
||||
// Reads all or part of a FGReplayData from Continuous file.
|
||||
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.
|
||||
//
|
||||
/* Writes one frame of continuous record information. */
|
||||
bool continuousWriteFrame(
|
||||
Continuous& continuous,
|
||||
FGReplayData* r,
|
||||
|
@ -78,18 +66,17 @@ bool continuousWriteFrame(
|
|||
SGPropertyNode_ptr config
|
||||
);
|
||||
|
||||
// Opens continuous recording file and writes header.
|
||||
//
|
||||
// If MetaData is unset, we initialise it by calling saveSetup(). Otherwise
|
||||
// should be already set up.
|
||||
//
|
||||
// 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
|
||||
// such information.
|
||||
//
|
||||
// If path_override is not "", we use it as the path (instead of the path
|
||||
// determined by saveSetup().
|
||||
//
|
||||
/* Opens continuous recording file and writes header.
|
||||
|
||||
If MetaData is unset, we initialise it by calling saveSetup(). Otherwise should
|
||||
be already set up.
|
||||
|
||||
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
|
||||
such information.
|
||||
|
||||
If path_override is not "", we use it as the path (instead of the path
|
||||
determined by saveSetup(). */
|
||||
SGPropertyNode_ptr continuousWriteHeader(
|
||||
Continuous& continuous,
|
||||
FGFlightRecorder* m_pRecorder,
|
||||
|
@ -98,32 +85,12 @@ SGPropertyNode_ptr continuousWriteHeader(
|
|||
FGTapeType tape_type
|
||||
);
|
||||
|
||||
// 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.
|
||||
//
|
||||
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
|
||||
);
|
||||
/* Replays one frame from Continuous recording.
|
||||
|
||||
Returns true on success, otherwise we failed to read from Continuous recording.
|
||||
*/
|
||||
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);
|
||||
|
|
|
@ -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.
|
||||
* Restore all properties with data from given buffer. */
|
||||
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* pBuffer = (_pNextBuffer && !_pNextBuffer->raw_data.empty()) ? &_pNextBuffer->raw_data.front() : nullptr;
|
||||
|
||||
double ratio = 1.0;
|
||||
if (pBuffer) {
|
||||
/* Replay signals. */
|
||||
int Offset = 0;
|
||||
double ratio = 1.0;
|
||||
if (pLastBuffer)
|
||||
{
|
||||
double NextSimTime = _pNextBuffer->sim_time;
|
||||
|
@ -953,13 +1029,57 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
|
|||
if (replay_main_view) {
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "SimTime=" << SimTime
|
||||
<< " 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) {
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "SimTime=" << SimTime
|
||||
<< " 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. */
|
||||
struct FGReplayData
|
||||
{
|
||||
bool load_signals;
|
||||
bool load_multiplayer;
|
||||
bool load_extra_properties;
|
||||
|
||||
double sim_time;
|
||||
|
||||
// Our aircraft state.
|
||||
|
|
Loading…
Reference in a new issue