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);

	//Add the Offset
	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.