mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 02:31:13 +02:00
Added six byte quaternion compression routines & tests
This commit is contained in:
parent
a251b9e3df
commit
818d1f4601
4 changed files with 137 additions and 0 deletions
|
@ -135,6 +135,87 @@ int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatO
|
||||||
return sizeof(quatParts);
|
return sizeof(quatParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define HI_BYTE(x) (uint8_t)(x >> 8)
|
||||||
|
#define LO_BYTE(x) (uint8_t)(0xff & x)
|
||||||
|
|
||||||
|
int packOrientationQuatToSixBytes(unsigned char* buffer, const glm::quat& quatInput) {
|
||||||
|
|
||||||
|
// find largest component
|
||||||
|
uint8_t largestComponent = 0;
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
if (fabs(quatInput[i]) > fabs(quatInput[largestComponent])) {
|
||||||
|
largestComponent = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that the sign of the dropped component is always negative.
|
||||||
|
glm::quat q = quatInput[largestComponent] > 0 ? -quatInput : quatInput;
|
||||||
|
|
||||||
|
const float MAGNITUDE = 1.0f / sqrt(2.0f);
|
||||||
|
const uint32_t NUM_BITS_PER_COMPONENT = 15;
|
||||||
|
const uint32_t RANGE = (1 << NUM_BITS_PER_COMPONENT) - 1;
|
||||||
|
|
||||||
|
// quantize the smallest three components into integers
|
||||||
|
uint16_t components[3];
|
||||||
|
for (int i = 0, j = 0; i < 4; i++) {
|
||||||
|
if (i != largestComponent) {
|
||||||
|
// transform component into 0..1 range.
|
||||||
|
float value = (q[i] + MAGNITUDE) / (2.0f * MAGNITUDE);
|
||||||
|
|
||||||
|
// quantize 0..1 into 0..range
|
||||||
|
components[j] = (uint16_t)(value * RANGE);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode the largestComponent into the high bits of the first two components
|
||||||
|
components[0] = (0x7fff & components[0]) | ((0x01 & largestComponent) << 15);
|
||||||
|
components[1] = (0x7fff & components[1]) | ((0x02 & largestComponent) << 14);
|
||||||
|
|
||||||
|
buffer[0] = HI_BYTE(components[0]);
|
||||||
|
buffer[1] = LO_BYTE(components[0]);
|
||||||
|
buffer[2] = HI_BYTE(components[1]);
|
||||||
|
buffer[3] = LO_BYTE(components[1]);
|
||||||
|
buffer[4] = HI_BYTE(components[2]);
|
||||||
|
buffer[5] = LO_BYTE(components[2]);
|
||||||
|
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& quatOutput) {
|
||||||
|
|
||||||
|
uint16_t components[3];
|
||||||
|
components[0] = ((uint16_t)(0x7f & buffer[0]) << 8) | buffer[1];
|
||||||
|
components[1] = ((uint16_t)(0x7f & buffer[2]) << 8) | buffer[3];
|
||||||
|
components[2] = ((uint16_t)(0x7f & buffer[4]) << 8) | buffer[5];
|
||||||
|
|
||||||
|
// largestComponent is encoded into the highest bits of the first 2 components
|
||||||
|
uint8_t largestComponent = ((0x80 & buffer[2]) >> 6) | ((0x80 & buffer[0]) >> 7);
|
||||||
|
|
||||||
|
const uint32_t NUM_BITS_PER_COMPONENT = 15;
|
||||||
|
const float RANGE = (float)((1 << NUM_BITS_PER_COMPONENT) - 1);
|
||||||
|
const float MAGNITUDE = 1.0f / sqrtf(2.0f);
|
||||||
|
float floatComponents[3];
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
floatComponents[i] = ((float)components[i] / RANGE) * (2.0f * MAGNITUDE) - MAGNITUDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// missingComponent is always negative.
|
||||||
|
float missingComponent = -sqrtf(1.0f - floatComponents[0] * floatComponents[0] - floatComponents[1] * floatComponents[1] - floatComponents[2] * floatComponents[2]);
|
||||||
|
|
||||||
|
for (int i = 0, j = 0; i < 4; i++) {
|
||||||
|
if (i != largestComponent) {
|
||||||
|
quatOutput[i] = floatComponents[j];
|
||||||
|
j++;
|
||||||
|
} else {
|
||||||
|
quatOutput[i] = missingComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's
|
// Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's
|
||||||
// http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde,
|
// http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde,
|
||||||
// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java)
|
// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java)
|
||||||
|
|
|
@ -97,6 +97,14 @@ int unpackFloatAngleFromTwoByte(const uint16_t* byteAnglePointer, float* destina
|
||||||
int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput);
|
int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput);
|
||||||
int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput);
|
int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput);
|
||||||
|
|
||||||
|
// alternate compression method that picks the smallest three quaternion components.
|
||||||
|
// and packs them into 15 bits each. An additional 2 bits are used to encode which component
|
||||||
|
// was omitted. Also because the components are encoded from the -1/sqrt(2) to 1/sqrt(2) which
|
||||||
|
// gives us some extra precision over the -1 to 1 range. The final result will have a maximum
|
||||||
|
// error of +- 4.3e-5 error per compoenent.
|
||||||
|
int packOrientationQuatToSixBytes(unsigned char* buffer, const glm::quat& quatInput);
|
||||||
|
int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& quatOutput);
|
||||||
|
|
||||||
// Ratios need the be highly accurate when less than 10, but not very accurate above 10, and they
|
// Ratios need the be highly accurate when less than 10, but not very accurate above 10, and they
|
||||||
// are never greater than 1000 to 1, this allows us to encode each component in 16bits
|
// are never greater than 1000 to 1, this allows us to encode each component in 16bits
|
||||||
int packFloatRatioToTwoByte(unsigned char* buffer, float ratio);
|
int packFloatRatioToTwoByte(unsigned char* buffer, float ratio);
|
||||||
|
|
|
@ -54,4 +54,51 @@ void GLMHelpersTests::testEulerDecomposition() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void testQuatCompression(glm::quat testQuat) {
|
||||||
|
|
||||||
|
float MAX_COMPONENT_ERROR = 4.3e-5f;
|
||||||
|
|
||||||
|
glm::quat q;
|
||||||
|
uint8_t bytes[6];
|
||||||
|
packOrientationQuatToSixBytes(bytes, testQuat);
|
||||||
|
unpackOrientationQuatFromSixBytes(bytes, q);
|
||||||
|
if (glm::dot(q, testQuat) < 0.0f) {
|
||||||
|
q = -q;
|
||||||
|
}
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(q.x, testQuat.x, MAX_COMPONENT_ERROR);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(q.y, testQuat.y, MAX_COMPONENT_ERROR);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(q.z, testQuat.z, MAX_COMPONENT_ERROR);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(q.w, testQuat.w, MAX_COMPONENT_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLMHelpersTests::testSixByteOrientationCompression() {
|
||||||
|
const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f));
|
||||||
|
const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
|
||||||
|
testQuatCompression(ROT_X_90);
|
||||||
|
testQuatCompression(ROT_Y_180);
|
||||||
|
testQuatCompression(ROT_Z_30);
|
||||||
|
testQuatCompression(ROT_X_90 * ROT_Y_180);
|
||||||
|
testQuatCompression(ROT_Y_180 * ROT_X_90);
|
||||||
|
testQuatCompression(ROT_Z_30 * ROT_X_90);
|
||||||
|
testQuatCompression(ROT_X_90 * ROT_Z_30);
|
||||||
|
testQuatCompression(ROT_Z_30 * ROT_Y_180);
|
||||||
|
testQuatCompression(ROT_Y_180 * ROT_Z_30);
|
||||||
|
testQuatCompression(ROT_X_90 * ROT_Y_180 * ROT_Z_30);
|
||||||
|
testQuatCompression(ROT_Y_180 * ROT_Z_30 * ROT_X_90);
|
||||||
|
testQuatCompression(ROT_Z_30 * ROT_X_90 * ROT_Y_180);
|
||||||
|
|
||||||
|
testQuatCompression(-ROT_X_90);
|
||||||
|
testQuatCompression(-ROT_Y_180);
|
||||||
|
testQuatCompression(-ROT_Z_30);
|
||||||
|
testQuatCompression(-(ROT_X_90 * ROT_Y_180));
|
||||||
|
testQuatCompression(-(ROT_Y_180 * ROT_X_90));
|
||||||
|
testQuatCompression(-(ROT_Z_30 * ROT_X_90));
|
||||||
|
testQuatCompression(-(ROT_X_90 * ROT_Z_30));
|
||||||
|
testQuatCompression(-(ROT_Z_30 * ROT_Y_180));
|
||||||
|
testQuatCompression(-(ROT_Y_180 * ROT_Z_30));
|
||||||
|
testQuatCompression(-(ROT_X_90 * ROT_Y_180 * ROT_Z_30));
|
||||||
|
testQuatCompression(-(ROT_Y_180 * ROT_Z_30 * ROT_X_90));
|
||||||
|
testQuatCompression(-(ROT_Z_30 * ROT_X_90 * ROT_Y_180));
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ class GLMHelpersTests : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private slots:
|
private slots:
|
||||||
void testEulerDecomposition();
|
void testEulerDecomposition();
|
||||||
|
void testSixByteOrientationCompression();
|
||||||
};
|
};
|
||||||
|
|
||||||
float getErrorDifference(const float& a, const float& b);
|
float getErrorDifference(const float& a, const float& b);
|
||||||
|
|
Loading…
Reference in a new issue