mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 19:04:32 +02:00
New recording file format
Added header containing: - Magic number - Version number - Data Offset - Data Length - CRC-32 Planned space for metadata Added context block (domain, models, transform, display name, attachments) Improved glm types packing (SHould take about half the space it used to))
This commit is contained in:
parent
ffba243a4f
commit
d80a42b800
1 changed files with 274 additions and 118 deletions
|
@ -24,6 +24,14 @@
|
|||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
// HFR file format magic number
|
||||
// (decimal) 17 72 70 82 13 10 26 10
|
||||
// (hexadecimal) 11 48 46 52 0d 0a 1a 0a
|
||||
// (ASCII C notation) \021 H F R \r \n \032 \n
|
||||
static const int MAGIC_NUMBER_SIZE = 8;
|
||||
static const char MAGIC_NUMBER[MAGIC_NUMBER_SIZE] = {17, 72, 70, 82, 13, 10, 26, 10};
|
||||
// Version (Major, Minor)
|
||||
static const QPair<quint8, quint8> VERSION(0, 1);
|
||||
|
||||
// TODO: remove operators
|
||||
void operator<<(QDebug& stream, glm::vec3 vector) {
|
||||
|
@ -444,122 +452,173 @@ void writeRecordingToFile(RecordingPointer recording, QString filename) {
|
|||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Writing recording to " << filename << ".";
|
||||
QElapsedTimer timer;
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::WriteOnly)){
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)){
|
||||
qDebug() << "Couldn't open " << filename;
|
||||
return;
|
||||
}
|
||||
timer.start();
|
||||
|
||||
qDebug() << "Writing recording to " << filename << ".";
|
||||
|
||||
QDataStream fileStream(&file);
|
||||
|
||||
// HEADER
|
||||
file.write(MAGIC_NUMBER, MAGIC_NUMBER_SIZE); // Magic number
|
||||
fileStream << VERSION; // File format version
|
||||
const qint64 dataOffsetPos = file.pos();
|
||||
fileStream << (quint16)17; // Save two empty bytes for the data offset
|
||||
const qint64 dataLengthPos = file.pos();
|
||||
fileStream << (quint32)42; // Save four empty bytes for the data offset
|
||||
const quint64 crc32Pos = file.pos();
|
||||
fileStream << (quint32)121; // Save four empty bytes for the CRC-32
|
||||
|
||||
|
||||
// METADATA
|
||||
// TODO
|
||||
|
||||
|
||||
|
||||
// CONTEXT
|
||||
RecordingContext& context = recording->getContext();
|
||||
// Domain
|
||||
fileStream << context.domain;
|
||||
// Position
|
||||
writeVec3(fileStream, context.position);
|
||||
// Orientation
|
||||
writeQuat(fileStream, context.orientation);
|
||||
// Scale
|
||||
writeFloat(fileStream, context.scale);
|
||||
// Head model
|
||||
fileStream << context.headModel;
|
||||
// Skeleton model
|
||||
fileStream << context.skeletonModel;
|
||||
// Display name
|
||||
fileStream << context.displayName;
|
||||
// Attachements
|
||||
fileStream << (quint8)context.attachments.size();
|
||||
foreach (AttachmentData data, context.attachments) {
|
||||
// Model
|
||||
fileStream << data.modelURL.toString();
|
||||
// Joint name
|
||||
fileStream << data.jointName;
|
||||
// Position
|
||||
writeVec3(fileStream, data.translation);
|
||||
// Orientation
|
||||
writeQuat(fileStream, data.rotation);
|
||||
// Scale
|
||||
writeFloat(fileStream, data.scale);
|
||||
}
|
||||
|
||||
// RECORDING
|
||||
fileStream << recording->_timestamps;
|
||||
|
||||
RecordingFrame& baseFrame = recording->_frames[0];
|
||||
int totalLength = 0;
|
||||
QBitArray mask;
|
||||
|
||||
// Blendshape coefficients
|
||||
fileStream << baseFrame._blendshapeCoefficients;
|
||||
totalLength += baseFrame._blendshapeCoefficients.size();
|
||||
|
||||
// Joint Rotations
|
||||
int jointRotationSize = baseFrame._jointRotations.size();
|
||||
fileStream << jointRotationSize;
|
||||
for (int i = 0; i < jointRotationSize; ++i) {
|
||||
fileStream << baseFrame._jointRotations[i].x << baseFrame._jointRotations[i].y << baseFrame._jointRotations[i].z << baseFrame._jointRotations[i].w;
|
||||
}
|
||||
totalLength += jointRotationSize;
|
||||
|
||||
// Translation
|
||||
fileStream << baseFrame._translation.x << baseFrame._translation.y << baseFrame._translation.z;
|
||||
totalLength += 1;
|
||||
|
||||
// Rotation
|
||||
fileStream << baseFrame._rotation.x << baseFrame._rotation.y << baseFrame._rotation.z << baseFrame._rotation.w;
|
||||
totalLength += 1;
|
||||
|
||||
// Scale
|
||||
fileStream << baseFrame._scale;
|
||||
totalLength += 1;
|
||||
|
||||
// Head Rotation
|
||||
fileStream << baseFrame._headRotation.x << baseFrame._headRotation.y << baseFrame._headRotation.z << baseFrame._headRotation.w;
|
||||
totalLength += 1;
|
||||
|
||||
// Lean Sideways
|
||||
fileStream << baseFrame._leanSideways;
|
||||
totalLength += 1;
|
||||
|
||||
// Lean Forward
|
||||
fileStream << baseFrame._leanForward;
|
||||
totalLength += 1;
|
||||
|
||||
for (int i = 1; i < recording->_timestamps.size(); ++i) {
|
||||
QBitArray mask(totalLength);
|
||||
for (int i = 0; i < recording->_timestamps.size(); ++i) {
|
||||
mask.fill(false);
|
||||
int maskIndex = 0;
|
||||
QByteArray buffer;
|
||||
QDataStream stream(&buffer, QIODevice::WriteOnly);
|
||||
RecordingFrame& previousFrame = recording->_frames[i - 1];
|
||||
RecordingFrame& previousFrame = recording->_frames[(i != 0) ? i - 1 : i];
|
||||
RecordingFrame& frame = recording->_frames[i];
|
||||
|
||||
// Blendshape coefficients
|
||||
for (int i = 0; i < frame._blendshapeCoefficients.size(); ++i) {
|
||||
if (frame._blendshapeCoefficients[i] != previousFrame._blendshapeCoefficients[i]) {
|
||||
stream << frame._blendshapeCoefficients[i];
|
||||
// Blendshape Coefficients
|
||||
quint32 numBlenshapes = frame.getBlendshapeCoefficients().size();
|
||||
stream << numBlenshapes;
|
||||
if (i == 0) {
|
||||
mask.resize(mask.size() + numBlenshapes);
|
||||
}
|
||||
for (int j = 0; j < numBlenshapes; ++j) {
|
||||
if (i == 0 ||
|
||||
frame._blendshapeCoefficients[j] != previousFrame._blendshapeCoefficients[j]) {
|
||||
writeFloat(stream, frame.getBlendshapeCoefficients()[j]);
|
||||
mask.setBit(maskIndex);
|
||||
}
|
||||
maskIndex++;
|
||||
++maskIndex;
|
||||
}
|
||||
|
||||
// Joint Rotations
|
||||
for (int i = 0; i < frame._jointRotations.size(); ++i) {
|
||||
if (frame._jointRotations[i] != previousFrame._jointRotations[i]) {
|
||||
stream << frame._jointRotations[i].x << frame._jointRotations[i].y << frame._jointRotations[i].z << frame._jointRotations[i].w;
|
||||
quint32 numJoints = frame.getJointRotations().size();
|
||||
stream << numJoints;
|
||||
if (i == 0) {
|
||||
mask.resize(mask.size() + numJoints);
|
||||
}
|
||||
for (int j = 0; j < numJoints; ++j) {
|
||||
if (i == 0 ||
|
||||
frame._jointRotations[j] != previousFrame._jointRotations[j]) {
|
||||
writeQuat(stream, frame._jointRotations[j]);
|
||||
mask.setBit(maskIndex);
|
||||
}
|
||||
maskIndex++;
|
||||
}
|
||||
|
||||
// Translation
|
||||
if (frame._translation != previousFrame._translation) {
|
||||
stream << frame._translation.x << frame._translation.y << frame._translation.z;
|
||||
if (i == 0) {
|
||||
mask.resize(mask.size() + 1);
|
||||
}
|
||||
if (i == 0 || frame._translation != previousFrame._translation) {
|
||||
writeVec3(stream, frame._translation);
|
||||
mask.setBit(maskIndex);
|
||||
}
|
||||
maskIndex++;
|
||||
|
||||
// Rotation
|
||||
if (frame._rotation != previousFrame._rotation) {
|
||||
stream << frame._rotation.x << frame._rotation.y << frame._rotation.z << frame._rotation.w;
|
||||
if (i == 0) {
|
||||
mask.resize(mask.size() + 1);
|
||||
}
|
||||
if (i == 0 || frame._rotation != previousFrame._rotation) {
|
||||
writeQuat(stream, frame._rotation);
|
||||
mask.setBit(maskIndex);
|
||||
}
|
||||
maskIndex++;
|
||||
|
||||
// Scale
|
||||
if (frame._scale != previousFrame._scale) {
|
||||
stream << frame._scale;
|
||||
if (i == 0) {
|
||||
mask.resize(mask.size() + 1);
|
||||
}
|
||||
if (i == 0 || frame._scale != previousFrame._scale) {
|
||||
writeFloat(stream, frame._scale);
|
||||
mask.setBit(maskIndex);
|
||||
}
|
||||
maskIndex++;
|
||||
|
||||
// Head Rotation
|
||||
if (frame._headRotation != previousFrame._headRotation) {
|
||||
stream << frame._headRotation.x << frame._headRotation.y << frame._headRotation.z << frame._headRotation.w;
|
||||
if (i == 0) {
|
||||
mask.resize(mask.size() + 1);
|
||||
}
|
||||
if (i == 0 || frame._headRotation != previousFrame._headRotation) {
|
||||
writeQuat(stream, frame._headRotation);
|
||||
mask.setBit(maskIndex);
|
||||
}
|
||||
maskIndex++;
|
||||
|
||||
// Lean Sideways
|
||||
if (frame._leanSideways != previousFrame._leanSideways) {
|
||||
stream << frame._leanSideways;
|
||||
if (i == 0) {
|
||||
mask.resize(mask.size() + 1);
|
||||
}
|
||||
if (i == 0 || frame._leanSideways != previousFrame._leanSideways) {
|
||||
writeFloat(stream, frame._leanSideways);
|
||||
mask.setBit(maskIndex);
|
||||
}
|
||||
maskIndex++;
|
||||
|
||||
// Lean Forward
|
||||
if (frame._leanForward != previousFrame._leanForward) {
|
||||
stream << frame._leanForward;
|
||||
if (i == 0) {
|
||||
mask.resize(mask.size() + 1);
|
||||
}
|
||||
if (i == 0 || frame._leanForward != previousFrame._leanForward) {
|
||||
writeFloat(stream, frame._leanForward);
|
||||
mask.setBit(maskIndex);
|
||||
}
|
||||
maskIndex++;
|
||||
|
||||
// LookAt Position
|
||||
if (i == 0) {
|
||||
mask.resize(mask.size() + 1);
|
||||
}
|
||||
if (i == 0 || frame._lookAtPosition != previousFrame._lookAtPosition) {
|
||||
writeVec3(stream, frame._lookAtPosition);
|
||||
mask.setBit(maskIndex);
|
||||
}
|
||||
maskIndex++;
|
||||
|
@ -570,22 +629,30 @@ void writeRecordingToFile(RecordingPointer recording, QString filename) {
|
|||
|
||||
fileStream << recording->_audio->getByteArray();
|
||||
|
||||
// TODO: Complete empty bytes
|
||||
file.seek(dataOffsetPos);
|
||||
file.seek(dataLengthPos);
|
||||
file.seek(crc32Pos);
|
||||
|
||||
qDebug() << "Wrote " << file.size() << " bytes in " << timer.elapsed() << " ms.";
|
||||
}
|
||||
|
||||
RecordingPointer readRecordingFromFile(RecordingPointer recording, QString filename) {
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
QByteArray byteArray;
|
||||
QUrl url(filename);
|
||||
QElapsedTimer timer;
|
||||
timer.start(); // timer used for debug informations (download/parsing time)
|
||||
|
||||
// Aquire the data and place it in byteArray
|
||||
// Return if data unavailable
|
||||
if (url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "ftp") {
|
||||
// Download file if necessary
|
||||
qDebug() << "Downloading recording at" << url;
|
||||
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
loop.exec(); // wait for file
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qDebug() << "Error while downloading recording: " << reply->error();
|
||||
reply->deleteLater();
|
||||
|
@ -593,112 +660,201 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, QString filen
|
|||
}
|
||||
byteArray = reply->readAll();
|
||||
reply->deleteLater();
|
||||
// print debug + restart timer
|
||||
qDebug() << "Downloaded " << byteArray.size() << " bytes in " << timer.restart() << " ms.";
|
||||
} else {
|
||||
// If local file, just read it.
|
||||
qDebug() << "Reading recording from " << filename << ".";
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::ReadOnly)){
|
||||
qDebug() << "Could not open local file: " << url;
|
||||
return recording;
|
||||
}
|
||||
byteArray = file.readAll();
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Reset the recording passed in the arguments
|
||||
if (!recording) {
|
||||
recording.reset(new Recording());
|
||||
}
|
||||
|
||||
QDataStream fileStream(byteArray);
|
||||
|
||||
fileStream >> recording->_timestamps;
|
||||
RecordingFrame baseFrame;
|
||||
// HEADER
|
||||
QByteArray magicNumber(MAGIC_NUMBER, MAGIC_NUMBER_SIZE);
|
||||
if (!byteArray.startsWith(magicNumber)) {
|
||||
qDebug() << "ERROR: This is not a .HFR file. (Magic Number incorrect)";
|
||||
return recording;
|
||||
}
|
||||
fileStream.skipRawData(MAGIC_NUMBER_SIZE);
|
||||
|
||||
// Blendshape coefficients
|
||||
fileStream >> baseFrame._blendshapeCoefficients;
|
||||
|
||||
// Joint Rotations
|
||||
int jointRotationSize;
|
||||
fileStream >> jointRotationSize;
|
||||
baseFrame._jointRotations.resize(jointRotationSize);
|
||||
for (int i = 0; i < jointRotationSize; ++i) {
|
||||
fileStream >> baseFrame._jointRotations[i].x >> baseFrame._jointRotations[i].y >> baseFrame._jointRotations[i].z >> baseFrame._jointRotations[i].w;
|
||||
QPair<quint8, quint8> version;
|
||||
fileStream >> version; // File format version
|
||||
if (version != VERSION) {
|
||||
qDebug() << "ERROR: This file format version is not supported.";
|
||||
return recording;
|
||||
}
|
||||
|
||||
fileStream >> baseFrame._translation.x >> baseFrame._translation.y >> baseFrame._translation.z;
|
||||
fileStream >> baseFrame._rotation.x >> baseFrame._rotation.y >> baseFrame._rotation.z >> baseFrame._rotation.w;
|
||||
fileStream >> baseFrame._scale;
|
||||
fileStream >> baseFrame._headRotation.x >> baseFrame._headRotation.y >> baseFrame._headRotation.z >> baseFrame._headRotation.w;
|
||||
fileStream >> baseFrame._leanSideways;
|
||||
fileStream >> baseFrame._leanForward;
|
||||
quint16 dataLength = 0;
|
||||
fileStream >> dataLength;
|
||||
quint32 dataOffset = 0;
|
||||
fileStream >> dataOffset;
|
||||
quint32 crc32 = 0;
|
||||
fileStream >> crc32;
|
||||
|
||||
recording->_frames << baseFrame;
|
||||
// METADATA
|
||||
// TODO
|
||||
|
||||
for (int i = 1; i < recording->_timestamps.size(); ++i) {
|
||||
|
||||
|
||||
// CONTEXT
|
||||
RecordingContext& context = recording->getContext();
|
||||
|
||||
// Domain
|
||||
fileStream >> context.domain;
|
||||
// Position
|
||||
if (!readVec3(fileStream, context.position)) {
|
||||
qDebug() << "Couldn't read file correctly. (Invalid vec3)";
|
||||
recording.clear();
|
||||
return recording;
|
||||
}
|
||||
// Orientation
|
||||
if (!readQuat(fileStream, context.orientation)) {
|
||||
qDebug() << "Couldn't read file correctly. (Invalid quat)";
|
||||
recording.clear();
|
||||
return recording;
|
||||
}
|
||||
|
||||
// Scale
|
||||
if (!readFloat(fileStream, context.scale)) {
|
||||
qDebug() << "Couldn't read file correctly. (Invalid float)";
|
||||
recording.clear();
|
||||
return recording;
|
||||
}
|
||||
// Head model
|
||||
fileStream >> context.headModel;
|
||||
// Skeleton model
|
||||
fileStream >> context.skeletonModel;
|
||||
// Display Name
|
||||
fileStream >> context.displayName;
|
||||
|
||||
// Attachements
|
||||
quint8 numAttachments = 0;
|
||||
fileStream >> numAttachments;
|
||||
for (int i = 0; i < numAttachments; ++i) {
|
||||
AttachmentData data;
|
||||
// Model
|
||||
fileStream >> data.modelURL;
|
||||
// Joint name
|
||||
fileStream >> data.jointName;
|
||||
// Translation
|
||||
if (!readVec3(fileStream, data.translation)) {
|
||||
qDebug() << "Couldn't read attachment correctly. (Invalid vec3)";
|
||||
continue;
|
||||
}
|
||||
// Rotation
|
||||
if (!readQuat(fileStream, data.rotation)) {
|
||||
qDebug() << "Couldn't read attachment correctly. (Invalid quat)";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scale
|
||||
if (!readFloat(fileStream, data.scale)) {
|
||||
qDebug() << "Couldn't read attachment correctly. (Invalid float)";
|
||||
continue;
|
||||
}
|
||||
context.attachments << data;
|
||||
}
|
||||
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
qDebug() << "File Format version:" << VERSION;
|
||||
qDebug() << "Data length:" << dataLength;
|
||||
qDebug() << "Data offset:" << dataOffset;
|
||||
qDebug() << "CRC-32:" << crc32;
|
||||
|
||||
qDebug() << "Context block:";
|
||||
qDebug() << "Domain:" << context.domain;
|
||||
qDebug() << "Position:" << context.position;
|
||||
qDebug() << "Orientation:" << context.orientation;
|
||||
qDebug() << "Scale:" << context.scale;
|
||||
qDebug() << "Head Model:" << context.headModel;
|
||||
qDebug() << "Skeleton Model:" << context.skeletonModel;
|
||||
qDebug() << "Display Name:" << context.displayName;
|
||||
qDebug() << "Num Attachments:" << numAttachments;
|
||||
|
||||
for (int i = 0; i < numAttachments; ++i) {
|
||||
qDebug() << "Model URL:" << context.attachments[i].modelURL;
|
||||
qDebug() << "Joint Name:" << context.attachments[i].jointName;
|
||||
qDebug() << "Translation:" << context.attachments[i].translation;
|
||||
qDebug() << "Rotation:" << context.attachments[i].rotation;
|
||||
qDebug() << "Scale:" << context.attachments[i].scale;
|
||||
}
|
||||
}
|
||||
|
||||
// RECORDING
|
||||
fileStream >> recording->_timestamps;
|
||||
|
||||
for (int i = 0; i < recording->_timestamps.size(); ++i) {
|
||||
QBitArray mask;
|
||||
QByteArray buffer;
|
||||
QDataStream stream(&buffer, QIODevice::ReadOnly);
|
||||
RecordingFrame frame;
|
||||
RecordingFrame& previousFrame = recording->_frames.last();
|
||||
RecordingFrame& previousFrame = (i == 0) ? frame : recording->_frames.last();
|
||||
|
||||
fileStream >> mask;
|
||||
fileStream >> buffer;
|
||||
int maskIndex = 0;
|
||||
|
||||
// Blendshape Coefficients
|
||||
frame._blendshapeCoefficients.resize(baseFrame._blendshapeCoefficients.size());
|
||||
for (int i = 0; i < baseFrame._blendshapeCoefficients.size(); ++i) {
|
||||
if (mask[maskIndex++]) {
|
||||
stream >> frame._blendshapeCoefficients[i];
|
||||
} else {
|
||||
frame._blendshapeCoefficients[i] = previousFrame._blendshapeCoefficients[i];
|
||||
quint32 numBlendshapes = 0;
|
||||
stream >> numBlendshapes;
|
||||
frame._blendshapeCoefficients.resize(numBlendshapes);
|
||||
for (int j = 0; j < numBlendshapes; ++j) {
|
||||
if (!mask[maskIndex++] || !readFloat(stream, frame._blendshapeCoefficients[j])) {
|
||||
frame._blendshapeCoefficients[j] = previousFrame._blendshapeCoefficients[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Joint Rotations
|
||||
frame._jointRotations.resize(baseFrame._jointRotations.size());
|
||||
for (int i = 0; i < baseFrame._jointRotations.size(); ++i) {
|
||||
if (mask[maskIndex++]) {
|
||||
stream >> frame._jointRotations[i].x >> frame._jointRotations[i].y >> frame._jointRotations[i].z >> frame._jointRotations[i].w;
|
||||
} else {
|
||||
frame._jointRotations[i] = previousFrame._jointRotations[i];
|
||||
quint32 numJoints = 0;
|
||||
stream >> numJoints;
|
||||
frame._jointRotations.resize(numJoints);
|
||||
for (int j = 0; j < numJoints; ++j) {
|
||||
if (!mask[maskIndex++] || !readQuat(stream, frame._jointRotations[j])) {
|
||||
frame._jointRotations[j] = previousFrame._jointRotations[j];
|
||||
}
|
||||
}
|
||||
|
||||
if (mask[maskIndex++]) {
|
||||
stream >> frame._translation.x >> frame._translation.y >> frame._translation.z;
|
||||
} else {
|
||||
if (!mask[maskIndex++] || !readVec3(stream, frame._translation)) {
|
||||
frame._translation = previousFrame._translation;
|
||||
}
|
||||
|
||||
if (mask[maskIndex++]) {
|
||||
stream >> frame._rotation.x >> frame._rotation.y >> frame._rotation.z >> frame._rotation.w;
|
||||
} else {
|
||||
if (!mask[maskIndex++] || !readQuat(stream, frame._rotation)) {
|
||||
frame._rotation = previousFrame._rotation;
|
||||
}
|
||||
|
||||
if (mask[maskIndex++]) {
|
||||
stream >> frame._scale;
|
||||
} else {
|
||||
if (!mask[maskIndex++] || !readFloat(stream, frame._scale)) {
|
||||
frame._scale = previousFrame._scale;
|
||||
}
|
||||
|
||||
if (mask[maskIndex++]) {
|
||||
stream >> frame._headRotation.x >> frame._headRotation.y >> frame._headRotation.z >> frame._headRotation.w;
|
||||
} else {
|
||||
if (!mask[maskIndex++] || !readQuat(stream, frame._headRotation)) {
|
||||
frame._headRotation = previousFrame._headRotation;
|
||||
}
|
||||
|
||||
if (mask[maskIndex++]) {
|
||||
stream >> frame._leanSideways;
|
||||
} else {
|
||||
if (!mask[maskIndex++] || !readFloat(stream, frame._leanSideways)) {
|
||||
frame._leanSideways = previousFrame._leanSideways;
|
||||
}
|
||||
|
||||
if (mask[maskIndex++]) {
|
||||
stream >> frame._leanForward;
|
||||
} else {
|
||||
if (!mask[maskIndex++] || !readFloat(stream, frame._leanForward)) {
|
||||
frame._leanForward = previousFrame._leanForward;
|
||||
}
|
||||
|
||||
if (!mask[maskIndex++] || !readVec3(stream, frame._lookAtPosition)) {
|
||||
frame._lookAtPosition = previousFrame._lookAtPosition;
|
||||
}
|
||||
|
||||
recording->_frames << frame;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue