-->
This plugins offers means to perform (lossy) compression (and decompression) of floating point numbers. And means to compress (and restore) rotation quaternions using the smallest three method. All functions of this class are static, meaning that no instance of it is required.
This class was originally created to help compress data related to object orientation for networking purposes. In several use cases the introduced error is small enough to not pose a problem. While storing the full float should not be a problem in modern days, this kind of compression is still useful to help reduce bandwidth requirements in networking code.
The functionality is accessed through the Quantize
class. The important thing to keep in mind is that the expected range of the number must be known, otherwise the compression will fail.
Now suppose we have a number that is in range [0..1]. We can compress it using the quantize_unit_float()
function. It requires two arguments, which are the value to be compressed and the number of bits to be used in the compression. The number of bits must be positive and at most 32. Depending on how Godot is compiled it doesn't make much sense to use 32 bits to compress the float number, as it might be the exact same amount of bits used by the float itself. That said, this function returns an integer. Note that in Godot (GDScript) the value returned is a full integer (32 or 64 bits) plus the Variant header. In this state it's not exactly useful, but the remaining bits can be safely discarded or maybe used to incorporate additional quantized floats through bit masking.
To restore a float that was originally in the [0..1] range and compressed with quantize_unit_float()
there is the restore_unit_float()
function. It requires the quantized integer data and the number of bits used to compress the original number. The return value is a float that should be close enough to the original one.
In practice it looks like this:
# Quantize a float in range [0..1] using 10 bits
var quantized: int = Quantize.quantize_unit_float(0.45, 10)
# ... some code
# Restore that quantized float
var restored: float = Quantize.restore_unit_float(quantized, 10)
In this case restored = 0.449658
. It does incorporate an error, which becomes smaller if the number of bits is increased.
What about different ranges? For that, there is the quantize_float()
function, which requires four arguments. The first one is the number to be compressed. Then there are the two ends of the range (minimum and maximum values, respectively). The last argument is the number of bits to be used in the compression. Much like quantize_unit_float()
, an integer is returned.
To restore that float there is the restore_float()
function, which also requires four arguments. First the integer containing the compressed data. Then the range. Finally the number of bits used to compress. Remember that minimum, maximum and bit count must the exact same used during the compression.
In practice, suppose we want to compress a number that is in range [-1..1], using 16 bits this time:
# Quantize a float in range [-1..1] using 16 bits
var quantized: int = Quantize.quantize_float(-0.35, -1.0, 1.0, 16)
# ... some code
# Restore the quantized float
var restored: float = Quantize.restore_float(quantized, -1.0, 1.0, 16)
This should result in restored = -0.349996
. That is basically all for simple floating point number quantization (compression)!
Then, as mentioned in the introduction, the plugin offers means to compress and restore rotation quaternions. The compression uses the smallest three method, which consists in finding the biggest component in the quaternion, "dropping" it then compressing the three remaining components. This information is required to understand the returned data used during the compression. What exactly happens behind the scenes is not required to understand and use this functionality.
First there is a "general" function, compress_rotation_quat()
. It requires two arguments, the quaternion to be compressed and the number of bits per component. The return value is a Dictionary
containing the following fields:
a
, b
and c
: those are the remaining compressed components. Basically each one stores the return value of quantize_float()
.index
: indicate which component of the quaternion was "dropped", (0 = x
, 1 = y
, 2 = z
and 3 = w
).sig
: while not entirely necessary, indicate the original signal of the component that got "dropped" (1 = positive, 0 = negative).To restore a rotation quaternion the restore_rotation_quat()
function is provided. This requires two arguments. The first one is the Dictionary
containing the quantized data, as it was returned by compress_rotation_quat()
. The second argument indicates the number of bits used to compress the rotation quaternion. The function returns the restored Quaternion
.
In practice, suppose we have a rotation quaternion named rquat
and want to compress it using 10 bits per component.
# Compress a rotation quaternion using 10 bits per component
var compressed: Dictionary = Quantize.compress_rotation_quat(rquat, 10)
# ... in here we could pack the components using bit masking, discarding unused bits
# Restore the quaternion
var restored: Quaternion = Quantize.restore_rotation_quat(compressed, 10)
Again the returned Dictionary
might not be very useful. Indeed, each field of it is using a full variant object, representing a full integer. However this is meant to serve as intermediary data. The compression only becomes useful when the result is packed into integers using bit masking.
To facilitate this packing, there are 3 "wrappers" to compress rotation quaternions. Those use 9, 10 or 15 bits per component. The first two cases result in data that can be packed into a single integer, which is the return value of those two cases. However 15 bits per component requires two integers to store the data. To that end the function returns a PackedIntArray
containing two integers, one that is fully used and the other that can discard 16 bits. The mentioned functions are compress_rquat_9bits()
, compress_rquat_10bits()
and compress_rquat_15bits()
. In all 3 functions a single argument is required, which is the original rotation quaternion.
Then, to restore those quaternions, restore_rquat_9bits()
, restore_rquat_10bits()
and restore_rquat_15bits()
. In the first two functions a single argument is required, which should match the returned value from the corresponding compression functions. For the 15 bits case however, two arguments are required, which should match the two elements of the array returned by the compression function. In practice:
# Compress a rotation quaternion using 9 bits per component
var compressed9: int = Quantize.compress_rquat_9bits(rquat)
# Compress a rotation quaternion using 10 bits per component
var compressed10: int = Quantize.compress_rquat_10bits(rquat)
# Compress a rotation quaternion using 15 bits per component - element [1] can discard 16 bits
var compressed15: PackedIntArray = Quantize.compress_rquat_15bits(rquat)
# Restore a rotation quaternion that was compressed using 9 bits per component
var restored9: Quaternion = Quantize.restore_rquat_9bits(compressed9)
# Restore a rotation quaternion that was compressed using 10 bits per component
var restored10: Quaternion = Quantize.restore_rquat_10bits(compressed10)
# Restore a rotation quaternion that was compressed using 15 bits per component
var restored15: Quaternion = Quantize.restore_rquat_15bits(compressed15[0], compressed15[1])