Add Quaternion quantization for network stream

Reduced send rate by 56% in average. Identity quat is just 8-bits (flags)
Added tests with full-sphere rotation to ensure this works correctly.
This commit is contained in:
Wojtek Figat
2024-10-14 22:32:29 +02:00
parent a6f1dbbf32
commit 7e4d7743a4
3 changed files with 170 additions and 0 deletions

View File

@@ -2,6 +2,94 @@
#include "NetworkStream.h"
#include "INetworkSerializable.h"
#include "Engine/Core/Math/Quaternion.h"
// Quaternion quantized for optimized network data size.
struct NetworkQuaternion
{
enum Flag : uint8
{
None = 0,
HasX = 1,
HasY = 2,
HasZ = 4,
NegativeX = 8,
NegativeY = 16,
NegativeZ = 32,
NegativeW = 64,
};
FORCE_INLINE static void Read(NetworkStream* stream, Quaternion& data)
{
uint8 flags;
stream->Read(flags);
if (flags == None)
{
// Early out on default value
data = Quaternion::Identity;
return;
}
Quaternion raw = Quaternion::Identity;
#define READ_COMPONENT(comp, hasFlag, negativeFlag) \
if (flags & hasFlag) \
{ \
uint16 packed; \
stream->Read(packed); \
const float norm = (float)packed / (float)MAX_uint16; \
raw.comp = norm; \
if (flags & negativeFlag) \
raw.comp = -raw.comp; \
}
READ_COMPONENT(X, HasX, NegativeX);
READ_COMPONENT(Y, HasY, NegativeY);
READ_COMPONENT(Z, HasZ, NegativeZ);
#define READ_COMPONENT
// Calculate W
raw.W = Math::Sqrt(Math::Max(1.0f - raw.X * raw.X - raw.Y * raw.Y - raw.Z * raw.Z, 0.0f));
if (flags & NegativeW)
raw.W = -raw.W;
raw.Normalize();
data = raw;
}
FORCE_INLINE static void Write(NetworkStream* stream, const Quaternion& data)
{
// Assumes rotation is normalized so W can be recalculated
Quaternion raw = data;
raw.Normalize();
// Compose flags that describe the data
uint8 flags = HasX | HasY | HasZ;
#define QUANTIZE_COMPONENT(comp, hasFlag, negativeFlag) \
if (Math::IsZero(raw.comp)) \
flags &= ~hasFlag; \
else if (raw.comp < 0.0f) \
flags |= negativeFlag
QUANTIZE_COMPONENT(X, HasX, NegativeX);
QUANTIZE_COMPONENT(Y, HasY, NegativeY);
QUANTIZE_COMPONENT(Z, HasZ, NegativeZ);
if (raw.W < 0.0f)
flags |= NegativeW;
#undef QUANTIZE_COMPONENT
// Write data
stream->Write(flags);
#define WRITE_COMPONENT(comp, hasFlag) \
if (flags & hasFlag) \
{ \
const float norm = Math::Abs(raw.comp); \
const uint16 packed = (uint16)(norm * MAX_uint16); \
stream->Write(packed); \
}
WRITE_COMPONENT(X, HasX);
WRITE_COMPONENT(Y, HasY);
WRITE_COMPONENT(Z, HasZ);
#undef WRITE_COMPONENT
}
};
NetworkStream::NetworkStream(const SpawnParams& params)
: ScriptingObject(params)
@@ -58,6 +146,11 @@ void NetworkStream::Read(INetworkSerializable* obj)
obj->Deserialize(this);
}
void NetworkStream::Read(Quaternion& data)
{
NetworkQuaternion::Read(this, data);
}
void NetworkStream::Write(INetworkSerializable& obj)
{
obj.Serialize(this);
@@ -68,6 +161,11 @@ void NetworkStream::Write(INetworkSerializable* obj)
obj->Serialize(this);
}
void NetworkStream::Write(const Quaternion& data)
{
NetworkQuaternion::Write(this, data);
}
void NetworkStream::Flush()
{
// Nothing to do