# Quaternion Coordinate Space Conversion. Yes I can. Deconversion... I need help.

I am using motion tracker data which is lovingly supplied to me in X-forward, Y-right, Z-up coordinate space. However, Epic’s third person hero character skeleton does not use this. So, I convert the data. No problem.

This struct contains all the data I need to make a conversion.

``````

struct FQuatConversion
{
int32 X;
int32 Y;
int32 Z;
bool bXNeg;
bool bYNeg;
bool bZNeg;
FRotator Offset;

FQuatConversion(int32 x, int32 y, int32 z, bool bx, bool by, bool bz, FRotator off)
{
X = x;
Y = y;
Z = z;
bXNeg = bx;
bYNeg = by;
bZNeg = bz;
Offset = off;
}

FQuatConversion()
{
X = 1;
Y = 2;
Z = 3;
bXNeg = false;
bYNeg = false;
bZNeg = false;
Offset = FRotator(0.f, 0.f, 0.f);
}

};

``````

Here are some examples for use with Epic’s skeleton

``````

//X Forawrd, Y Left, Z Down hand_l
LHOffset = FQuatConversion(1, 2, 3, false, true, true, FRotator(0.f, 90.f, 180.f));
//X Back, Y Left, Z Up hand_r
RHOffset = FQuatConversion(1, 2, 3, true, true, false, FRotator(0.f, -90.f, 0.f));
//X Up, Y Forward, Z Right pelvis
HipsOffset = FQuatConversion(3, 1, 2, false, false, false, FRotator(90.f, 0.f, 0.f));
//X Up, Y Forward, Z Right spine_3
TorsoOffset = FQuatConversion(3, 1, 2, false, false, false, FRotator(90.f, 0.f, 0.f));
//X Up, Y Forward, Z Right head
HeadOffset = FQuatConversion(3, 1, 2, false, false, false, FRotator(90.f, 0.f, 0.f));

``````

Then this function can carry out the conversion

``````

FQuat AProtoCharacter::CharacterToMeshQuat(FQuat InQuat, FQuatConversion InCon)
{
float NX, NY, NZ, NW;

switch (InCon.X)
{
case 1:
NX = InQuat.X;
break;
case 2:
NX = InQuat.Y;
break;
case 3:
NX = InQuat.Z;
break;
default:
NX = 0.f;
break;
}

switch (InCon.Y)
{
case 1:
NY = InQuat.X;
break;
case 2:
NY = InQuat.Y;
break;
case 3:
NY = InQuat.Z;
break;
default:
NY = 0.f;
break;
}

switch (InCon.Z)
{
case 1:
NZ = InQuat.X;
break;
case 2:
NZ = InQuat.Y;
break;
case 3:
NZ = InQuat.Z;
break;
default:
NZ = 0.f;
break;
}

NW = InQuat.W;

if (InCon.bXNeg)
{
NX *= -1;
}

if (InCon.bYNeg)
{
NY *= -1;
}

if (InCon.bZNeg)
{
NZ *= -1;
}

FQuat NewQuat = FQuat(NX, NY, NZ, NW);

NewQuat = InCon.Offset.Quaternion() * NewQuat;

return NewQuat;
}

``````

It works great. Perfect mapping of motion to the skeleton. No gimbal lock.
Now my problem is, there are scenarios where I need to work backward from the converted data.
So, I try to reverse the order of operations. Unfortunately, I get a crash when I try to use the following function.

``````

FQuat AProtoCharacter::MeshToCharacterQuat(FQuat InQuat, FQuatConversion InCon)
{
float NX = 0.f, NY = 0.f, NZ = 0.f, NW = 0.f;
float IX = 0.f, IY = 0.f, IZ = 0.f, IW = 0.f;
FQuat NewQuat;
FQuat NQuat = InCon.Offset.Quaternion().Inverse();

//First, remove that offset;
FQuat IQuat = NQuat * InQuat;

//Second unflip the axis
if (InCon.bXNeg)
{
IX = IQuat.X * -1;
}
else
{
IX = IQuat.X;
}

if (InCon.bYNeg)
{
IY = IQuat.Y * -1;
}
else
{
IY = IQuat.Y;
}

if (InCon.bZNeg)
{
IZ = IQuat.Z * -1;
}
else
{
IZ = IQuat.Z;
}

IW = InQuat.W;

IQuat = FQuat(IX, IY, IZ, IW);

//Third unrearrange the axis
switch (InCon.X)
{
case 1:
NX = IQuat.X;
break;
case 2:
NY = IQuat.X;
break;
case 3:
NZ = IQuat.X;
break;
default:
break;
}

switch (InCon.Y)
{
case 1:
NX = IQuat.Y;
break;
case 2:
NY = IQuat.Y;
break;
case 3:
NZ = IQuat.Y;
break;
default:
break;
}

switch (InCon.Z)
{
case 1:
NX = IQuat.Z;
break;
case 2:
NY = IQuat.Z;
break;
case 3:
NZ = IQuat.Z;
break;
default:
break;
}

NW = IQuat.W;

//Fourth, put everything together
NewQuat = FQuat(NX, NY, NZ, NW);

return NewQuat;
}

``````

Access violation - code c0000005 (first/second chance not available)

UE4Editor_Proto!AProtoCharacter::MeshToCharacterQuat() + 112 bytes

Looks like the line FQuat IQuat = NQuat * InQuat; is what is causing the crash. No idea why.

Any help is much appreciated.

``````

FQuat AProtoCharacter::MeshToCharacterQuat(FQuat InQuat, FQuatConversion InCon)
{

float NX = 0.f, NY = 0.f, NZ = 0.f, NW = 0.f;
float IX = 0.f, IY = 0.f, IZ = 0.f, IW = 0.f;

FQuat NQuat = InCon.Offset.Quaternion().Inverse();
FQuat PQuat = InQuat;
//First, remove that offset;
FQuat IQuat = NQuat * PQuat;

//Second unflip the axis
if (InCon.bXNeg)
{
IX = IQuat.X * -1;
}
else
{
IX = IQuat.X;
}

if (InCon.bYNeg)
{
IY = IQuat.Y * -1;
}
else
{
IY = IQuat.Y;
}

if (InCon.bZNeg)
{
IZ = IQuat.Z * -1;
}
else
{
IZ = IQuat.Z;
}

IW = InQuat.W;

IQuat = FQuat(IX, IY, IZ, IW);

//Third unrearrange the axis
switch (InCon.X)
{
case 1:
NX = IQuat.X;
break;
case 2:
NY = IQuat.X;
break;
case 3:
NZ = IQuat.X;
break;
default:
NX = IQuat.X;
break;
}

switch (InCon.Y)
{
case 1:
NX = IQuat.Y;
break;
case 2:
NY = IQuat.Y;
break;
case 3:
NZ = IQuat.Y;
break;
default:
NY = IQuat.Y;
break;
}

switch (InCon.Z)
{
case 1:
NX = IQuat.Z;
break;
case 2:
NY = IQuat.Z;
break;
case 3:
NZ = IQuat.Z;
break;
default:
NZ = IQuat.Z;
break;
}

NW = IQuat.W;

//Fourth, put everything together
FQuat NewQuat = FQuat(NX, NY, NZ, NW);

return NewQuat;
}

``````

This doesn’t crash, but the math is wrong.

Well, screw having a deconversion function. Much better to have inverse function for the conversion struct.

``````

struct FQuatConversion
{
int32 X;
int32 Y;
int32 Z;
bool bXNeg;
bool bYNeg;
bool bZNeg;
FRotator Offset;

FQuatConversion(int32 x, int32 y, int32 z, bool bx, bool by, bool bz, FRotator off)
{
X = x;
Y = y;
Z = z;
bXNeg = bx;
bYNeg = by;
bZNeg = bz;
Offset = off;
}

FQuatConversion()
{
X = 1;
Y = 2;
Z = 3;
bXNeg = false;
bYNeg = false;
bZNeg = false;
Offset = FRotator(0.f, 0.f, 0.f);
}

FQuatConversion Inverse()
{
FQuatConversion NewCon;

switch (X)
{
case 1:
NewCon.X = 1;
break;
case 2:
NewCon.Y = 1;
break;
case 3:
NewCon.Z = 1;
break;
default:
NewCon.X = 1;
break;
}

switch (Y)
{
case 1:
NewCon.X = 2;
break;
case 2:
NewCon.Y = 2;
break;
case 3:
NewCon.Z = 2;
break;
default:
NewCon.X = 2;
break;
}

switch (Z)
{
case 1:
NewCon.X = 3;
break;
case 2:
NewCon.Y = 3;
break;
case 3:
NewCon.Z = 3;
break;
default:
NewCon.X = 3;
break;
}

NewCon.bXNeg = bXNeg;
NewCon.bYNeg = bYNeg;
NewCon.bZNeg = bZNeg;
NewCon.Offset = -1.f * Offset;

return NewCon;
}

};

``````

Feeding this inverse into the original conversion function works perfectly.