Convert ECEF Position and Quaternion into Unreal Engine 'format'

RE: ECEF → Unreal position and rotation transformation.

Greetings!

I am building a flight simulator visualization tool. I have an external program which streams aircraft state information (ECEF position and ECEF quaternion) into Unreal Engine. I have setup a Cesium world terrain and added a CesiumGeoreference. The origin of my CesiumGeoreference is the same as the initial latitude, longitude, altitude conditions of my external simulator.

In C++ Unreal, I am trying to set the position and orientation of an aircraft. This aircraft is instantiated in as an InstancedMeshComponent. I am attempting to set its position and rotation by modifying its corresponding InstanceTransform, like so:

FTransform InstanceTransform(
    EngineRotationQuat,
    EngineLocation,
    FVector(1.0f, 1.0f, 1.0f)
);

InstancedMeshComponent->UpdateInstanceTransform(
			*InstanceIndexPtr,
			InstanceTransform,
			false, // don't update bounds
			true // mark render state dirty
		);

Currently, I am (incorrectly) performing the ECEF->unreal transformation like so:
EngineRotationQuat and EngineLocation are currently being computed like so:

	GeoReferencingSystem->ECEFToEngine(position_ecf_m, EngineLocation);

	FMatrix ECEFRotationMatrix = quaternion_ecf.ToMatrix();

	FMatrix ECEF2Unreal = CesiumGeoreference-> ComputeEastSouthUpAtEarthCenteredEarthFixedPositionToUnrealTransformation(position_ecf_m);

	FMatrix UnrealRotationMatrix = ECEF2Unreal * ECEFRotationMatrix;

	EngineRotationQuat = UnrealRotationMatrix.ToQuat();

When I send the ECEF quaternion corresponding to level flight (not the pure quaternion), I expect to see my aircraft in unreal with a ‘straight an level’ attitude, but this is not the case.

For context, I have implemented an equivalent functional aircraft renderer in CesiumJS by simply setting the position and quaternion of the entity without any transformations, and it works perfectly:

        viewer.entities.add({
            name: "Aircraft",
            position: new Cesium.CallbackProperty(() => this.getPosition(), false),
            orientation: new Cesium.CallbackProperty(() => this.getOrientation(), false)
        });

Any help would be greatly appreciated. Thanks!

Hi @hillbillyhandfishing, welcome to the community!

I think the method on CesiumGeoreference that you want is ComputeEarthCenteredEarthFixedToUnrealTransformation:

This will return an FMatrix that transforms from ECEF to Unreal world coordinates. I think if you just swap that in for ComputeEastSouthUpAtEarthCenteredEarthFixedPositionToUnrealTransformation, you’ll be in much better shape.

You may need to swap the order of ECEF2Unreal * ECEFRotationMatrix as well, because FMatrix confusingly reverses the usual convention for matrix multiplication.

Finally, you may want to use this same matrix to set the EngineLocation instead of the GeoReferencingSystem. Something like:

EngineLocation = ECEF2Unreal.TransformPosition(position_ecf_m);

Hopefully that will be closer. Let me know how you go.

I’m actually having the same problem referenced here, where I need to manually calculate the conversion from ECEF orientation to Unreal orientation to drive some custom rendering code.

I’ve used the following code based on the comments above:

FMatrix TransformMat = ComputeEarthCenteredEarthFixedToUnrealTransformation();
TransformMat.RemoveScaling();
const FMatrix ECEFRotationMatrix = ECEFQuaternion.ToMatrix();
const FMatrix UnrealRotMatrix = TransformMat * ECEFRotationMatrix; // Also tried ECEFRotationMatrix * TransformMat unsuccessfully
return UnrealRotMatrix.ToQuat();

I’m also seeing an odd result, where my object is not level as it should be (and is on the web version reference implementation). There’s an odd canted angle in there that doesn’t seem to make much sense (if anything, I figured I’d be some set of 90 degree rotations off, but this probably about 25 degrees off where it should be to my eye).

Any help in figuring out what might be going on would be greatly appreciated!

Okay, after some investigation, I figured out what’s happening. Since a few other people on the forums seem to have this problem when using the Transformation matrices directly for things like Instanced Static Meshes, posting here to hopefully save some folks some time.

The problem is that ECEF is a right-handed coordinate system, and Unreal is a left-handed one. Unfortunately, that means there’s no purely rotational transform that will get you from one to the other. That’s fine! You just need to include scale in the transformation so that you can add reflections as opposed to just pure rotations, and everything will be fine.

However, when doing this in Unreal, you have to be careful! In my code above, I foolishly added:

TransformMat.RemoveScaling();

Because I was getting around a crash when performing the rotational operations. However, because of that, it means that you’ll never be able to actually properly transform from the ECEF to Unreal coordinate system. Positional transform functions in Unreal account for scale when transforming positions, but they almost always error out on rotational ones. My guess is that this is an optimization to try and save some cost on matrix computations by allowing you to avoid scales on any rotational operations, because it probably assumes you’re already in the LHS coordinate system already. That’s a good assumption, but you have to know about it. A few of the Unreal functions also silently drop scale when performing these operations, so you have to be careful which ones you use.

So, to convert the positions, here’s some sample code. It can be simplified, but leaving it verbose to hopefully make it clear what’s going on:

const FMatrix Transform = ComputeEarthCenteredEarthFixedToUnrealTransformation();
const FMatrix FinalRotationMatrix = (ECEFQuaternion.ToMatrix() * Transform);

const FVector RotationalXAxis = FinalRotationMatrix.GetScaledAxis(EAxis::X);
const FVector RotationalYAxis = FinalRotationMatrix.GetScaledAxis(EAxis::Y);
const FQuat UnrealQuat = FRotationMatrix::MakeFromXY(RotationalXAxis, RotationalYAxis).ToQuat();

Hopefully that helps someone out there!

1 Like