Hi all! My name is Chris Hargrove; I wrote Golem.
(I may have another account on this forum with my full name, but I lost my login info that a long time ago and I know my email address has changed, but whatever).
Anyway, I first noticed this thread last year in passing, but stumbled onto it again today; I'm stunned and impressed that people have done so much work to reverse-engineer the .gem format! I know some parts of it aren't the easiest to work with from the outside looking in.
It being nearly a decade since I wrote that stuff, I'm pretty sure nobody would care if I gave people format information (that was one of the things we originally intended to do back when I was at Legend)... however "pretty sure" doesn't hold up legally, and the problem is ever since Legend was closed down by Infogrames, I have absolutely no idea who I would have to talk to in order to release information like that. So I apologize profusely for that.
That said, I'm not sure if this exporter project is still happening, but if it is: one thing I *can* tell you is how those 5-byte rotations work, because it's something I wrote long before Golem, and which I've used both before and since then, so it's not tied to that specific implementation and hence no legal concerns there. Since it's something you folks indicated was a bit of a blocking point, I figured I could elucidate it here.
The basic idea is that it's a packed axis & angle, which you can use to put into a quaternion. 5 bytes was enough to encode them both with sufficient precision; I did experiments with 4 bytes but it just looked too jittery. I probably could have gotten into 4 bytes most of the time if I allowed some kind of VBR scheme for the edge cases, but having a fixed-size structure was extremely helpful at runtime (despite the odd size).
Anyway, here's some code for it. I've sanitized it a bit so hopefully I didn't break anything in the process, but hopefully you can figure out what's going on.
BTW, when you see typecasts in there, note that the code is based on a little-endian CPU (such as x86). If you're operating on a big-endian CPU, adjust accordingly.
Code:
enum
{
PACKED_AXIS_SIGNMASK = 0xE00000,
PACKED_AXIS_XSIGNMASK = 0x800000,
PACKED_AXIS_YSIGNMASK = 0x400000,
PACKED_AXIS_ZSIGNMASK = 0x200000,
PACKED_AXIS_XMASK = 0x1FF800,
PACKED_AXIS_YMASK = 0x0007FF
};
VECTOR3 UnpackAxis(unsigned long p)
{
// get the x and y bits
signed long xBits = (p & PACKED_AXIS_XMASK) >> 11;
signed long yBits = p & PACKED_AXIS_YMASK;
// map the numbers into the projection triangle
if ((xBits + yBits) >= 2047)
{
xBits = 2047 - xBits;
yBits = 2047 - yBits;
}
// build vector
VECTOR3 result;
result.x = (float)xBits;
result.y = (float)yBits;
result.z = (float)(2046 - xBits - yBits);
// restore sign bits
if (p & PACKED_AXIS_XSIGNMASK) result.x = -result.x;
if (p & PACKED_AXIS_YSIGNMASK) result.y = -result.y;
if (p & PACKED_AXIS_ZSIGNMASK) result.z = -result.z;
// need to post-normalize since there's no normalization table
float s = 1.f / (float)sqrt(result.x*result.x + result.y*result.y + result.z*result.z);
result.x *= s;
result.y *= s;
result.z *= s;
return result;
}
QUAT UnpackQuat(unsigned char* fiveBytes)
{
// extract packed axis and angle from byte buffer
unsigned long packAxis = *((unsigned long*)fiveBytes);
packAxis &= 0x00FFFFFF; // only uses low 24 bits
fiveBytes += 3;
unsigned long packAngle = *((unsigned short*)fiveBytes);
// unpack axis and angle
VECTOR3 axis = UnpackAxis(packAxis);
float angle = ((float)packAngle)*PI/32768.f;
// generate result quaternion from axis and angle
float s = (float)sin(angle*0.5f);
QUAT result;
result.v.x = axis.x * s;
result.v.y = axis.y * s;
result.v.z = axis.z * s;
result.s = (float)cos(angle*0.5f);
return result;
}
Hope that helps!
- Chris