4

I wrote a plugin for UE4 for matching and spawning Static Mesh Actors into the level of UE4.
The plugin reads coordinates from a text file (Scale,Rotation,Transformation) which are exported from Softimage XSI per script. Everything works already. BUT not the Rotations.

I know it has something to do with the coordinate system. But how exactly do i convert from one to the other?

What i think that i found out so far (not 100% sure)
XSI is Right handed Y-up , Rotation Order XYZ
UE4 is Left handed Z-up, Rotation Order XZY

In both applications i have Euler angles in degrees.
So in my 3D software (Softimage XSI) i have XYZ degrees which i store to a text file on disk.
Line by line where each line is an object.
In UE4 the plugin reads this lines and spawns an SM Actor to the level.

+++++ New Information +++++

Hello, thanks for the answers so far!

I made a video to show the details, and to show the circumstances.

https://www.youtube.com/watch?v=sWX84FxZTw0


+++++ The Actor spawn function +++++

void SpawnSMActor(const TCHAR *path,float sX,float sY,float sZ,float rX,float rY,float rZ,float pX,float pY,float pZ) 
{
    // Load Static Mesh from given Reference Path from UE4 Explorer
    UStaticMesh* StaMesh = LoadObject<UStaticMesh>(nullptr, path);

    // Transform
    FVector objectScale(sX, sY, sZ);        // Scale

    // ********************************************************************************
    // Conversion XSI Coordinate System to UE4 Coordinate System

    FVector NewPosition;
    FRotator NewRotation;

    // We just simply swap the Z and Y Coordinates

    NewPosition.X = pX * 100; //    TX
    NewPosition.Y = pZ * 100; //    TZ
    NewPosition.Z = pY * 100; //    TY

    // We just simply swap the Pitch(Y) and Yaw(Z) angles

    NewRotation.Roll =  rX;     //  RX
    NewRotation.Pitch = rZ;     //  RZ
    NewRotation.Yaw =   -rY;    //  RY

    FRotator NewobjectRotation(NewRotation.Quaternion());

    FTransform objectTransform(NewobjectRotation, NewPosition, objectScale);

    // ********************************************************************************

    // Creating the Actor and Positioning it in the World based on the Static Mesh
    UWorld* currentWorld = GEditor->GetEditorWorldContext().World();
    ULevel* currentLevel = currentWorld->GetCurrentLevel();
    UClass* StaticMeshClass = AStaticMeshActor::StaticClass();
    AActor* NewActorCreated = GEditor->AddActor(currentLevel, StaticMeshClass, objectTransform, true, RF_Public | RF_Standalone | RF_Transactional);
    AStaticMeshActor* smActor = Cast<AStaticMeshActor>(NewActorCreated);

    smActor->GetStaticMeshComponent()->SetStaticMesh(StaMesh);
    smActor->SetActorScale3D(objectScale);

    // ID Name & Visible Name
    //smActor->Rename(TEXT("MyStaticMeshInTheWorld"));
    //smActor->SetActorLabel("MyStaticMeshInTheWorld");

    GEditor->EditorUpdateComponents();
    smActor->GetStaticMeshComponent()->RegisterComponentWithWorld(currentWorld);
    currentWorld->UpdateWorldComponents(true, false);
    smActor->RerunConstructionScripts();
    GLevelEditorModeTools().MapChangeNotify();
}


If i rotate an object in my 3D app by 45 degree clockwise on the UP axis
and 45 degree on the X-axis i get:

X -54,7356 °
Y -30 °
Z 35,2644 °

If i do the same Rotations in UE4 i get:

X -35,2644 °
Y 30 °
Z 35,2644 °

So those would be the correct rotation angles in UE4!

But with the code listed above i get:

X -54,7355 °
Y 35,2643 °
Z 30 °

And thats wrong! Because it just flips some positions as it seems. The angles are essentialy the same as from my 3D app.

Picture



Here is my second attempt to solve the conversion without the UE4 API.
I know its not complete and i still dont fully understand the steps i will have to take.

But its hopefully a start.
As @DavidC.Rankin menntioned above i need to define the (1) the original coordinate system.
Not sure if this is what is necessary but i put the info from Wikipedia about Euler to matrix in C++ code.
This are all 6 of the Tait–Bryan angles matrices:
Is this correct? And if so how will i define
(2) the target coordinate system ?
(3) the order of rotations ?

#include "pch.h"
#include <iostream>
#include <string>
#include "linalg.h"
using namespace linalg::aliases;
using namespace std;

float x,y,z;

//Pre

void MatrixXZY(float3 angles, float3x3& matrix);
void MatrixXYZ(float3 angles, float3x3& matrix);
void MatrixYXZ(float3 angles, float3x3& matrix);
void MatrixYZX(float3 angles, float3x3& matrix);
void MatrixZYX(float3 angles, float3x3& matrix);
void MatrixZXY(float3 angles, float3x3& matrix);

void PrintMatrix(string name, float3 angles, float3x3& matrix);
void MatrixDecomposeYXZ(float3x3& matrix, float3& angles);




int main()
{
    float3 AnglesIn = { 0, 0, 0 };
    float3 AnglesOut;
    float3x3 Matrix;  // Matrix [Spalte][Zeile]

    cout << "-----------------------------" << endl;
    cout << "Input" << endl;
    cout << AnglesIn[0] << " " << AnglesIn[1] << " " << AnglesIn[2] << " " << endl;
    cout << "-----------------------------" << endl << endl;

    MatrixXZY(AnglesIn, Matrix);
    PrintMatrix("XZY", AnglesIn, Matrix);
    MatrixXYZ(AnglesIn, Matrix);
    PrintMatrix("XYZ", AnglesIn, Matrix);

    MatrixYXZ(AnglesIn, Matrix);
    PrintMatrix("YXZ", AnglesIn, Matrix);
    MatrixDecomposeYXZ(Matrix, AnglesOut);
    cout << "-----------------------------" << endl;
    cout << AnglesOut.x << " " << AnglesOut.y << " " << AnglesOut.z << " " << endl;
    cout << "-----------------------------" << endl << endl;


    MatrixYZX(AnglesIn, Matrix);
    PrintMatrix("YZX", AnglesIn, Matrix);

    MatrixZYX(AnglesIn, Matrix);
    PrintMatrix("ZYX", AnglesIn, Matrix);
    MatrixZXY(AnglesIn, Matrix);
    PrintMatrix("ZXY", AnglesIn, Matrix);

}



void MatrixXZY(float3 angles, float3x3& matrix)
{
    float cosX = cosf(angles.x);     // X
    float sinX = sinf(angles.x);

    float cosY = cosf(angles.y);     // Y
    float sinY = sinf(angles.y);

    float cosZ = cosf(angles.z);     // Z
    float sinZ = sinf(angles.z);


    matrix[0][0] = cosZ * cosY;                         // Spalte 1
    matrix[0][1] = sinX * sinY + cosX * cosY * sinZ;
    matrix[0][2] = cosY * sinX * sinZ - cosX * sinY;

    matrix[1][0] = -sinZ;                               // Spalte 2
    matrix[1][1] = cosX * cosZ;
    matrix[1][2] = cosZ * sinX;

    matrix[2][0] = cosZ * sinY;                         // Spalte 3
    matrix[2][1] = cosX * sinZ * sinY - cosY * sinX;
    matrix[2][2] = cosX * cosY + sinX * sinZ * sinY;

}
void MatrixXYZ(float3 angles, float3x3& matrix)
{
    float cosX = cosf(angles.x);     // X
    float sinX = sinf(angles.x);

    float cosY = cosf(angles.y);     // Y
    float sinY = sinf(angles.y);

    float cosZ = cosf(angles.z);     // Z
    float sinZ = sinf(angles.z);


    matrix[0][0] = cosY * cosZ;                         // Spalte 1
    matrix[0][1] = cosX * sinZ + cosZ * sinX * sinY;
    matrix[0][2] = sinX * sinZ - cosX * cosZ * sinY;

    matrix[1][0] = -cosY * sinZ;                        // Spalte 2
    matrix[1][1] = cosX * cosZ - sinX * sinY * sinZ;
    matrix[1][2] = cosZ * sinX + cosX * sinY * sinZ;

    matrix[2][0] = sinY;                                // Spalte 3
    matrix[2][1] = -cosY * sinX;
    matrix[2][2] = cosX * cosY;

}

void MatrixYXZ(float3 angles, float3x3& matrix)
{
    float cosX = cosf(angles.x);     // X
    float sinX = sinf(angles.x);

    float cosY = cosf(angles.y);     // Y
    float sinY = sinf(angles.y);

    float cosZ = cosf(angles.z);     // Z
    float sinZ = sinf(angles.z);

    matrix[0][0] = cosY * cosZ + sinY * sinX * sinZ;    // Spalte 1
    matrix[0][1] = cosX * sinZ;
    matrix[0][2] = cosY * sinX * sinZ - cosZ * sinY;

    matrix[1][0] = cosZ * sinY * sinX - cosY * sinZ;    // Spalte 2
    matrix[1][1] = cosX * cosZ;
    matrix[1][2] = cosY * cosZ * sinX + sinY * sinZ;

    matrix[2][0] = cosX * sinY;                         // Spalte 3
    matrix[2][1] = -sinX;
    matrix[2][2] = cosY * cosX;

}
void MatrixYZX(float3 angles, float3x3& matrix)
{
    float cosX = cosf(angles.x);     // X
    float sinX = sinf(angles.x);

    float cosY = cosf(angles.y);     // Y
    float sinY = sinf(angles.y);

    float cosZ = cosf(angles.z);     // Z
    float sinZ = sinf(angles.z);


    matrix[0][0] = cosY * cosZ;                         // Spalte 1
    matrix[0][1] = sinZ;
    matrix[0][2] = -cosZ * sinY;

    matrix[1][0] = sinY * sinX - cosY * cosX * sinZ;    // Spalte 2
    matrix[1][1] = cosZ * cosX;
    matrix[1][2] = cosY * sinX + cosX * sinY * sinZ;

    matrix[2][0] = cosX * sinY + cosY * sinZ * sinX;    // Spalte 3
    matrix[2][1] = -cosZ * sinX;
    matrix[2][2] = cosY * cosX - sinY * sinZ * sinX;

}

void MatrixZYX(float3 angles, float3x3& matrix)
{
    float cosX = cosf(angles.x);     // X
    float sinX = sinf(angles.x);

    float cosY = cosf(angles.y);     // Y
    float sinY = sinf(angles.y);

    float cosZ = cosf(angles.z);     // Z
    float sinZ = sinf(angles.z);


    matrix[0][0] = cosZ * cosY;                         // Spalte 1
    matrix[0][1] = cosY * sinZ;
    matrix[0][2] = -sinY;

    matrix[1][0] = cosZ * sinY * sinX - cosX * sinZ;    // Spalte 2
    matrix[1][1] = cosZ * cosX + sinZ * sinY * sinX;
    matrix[1][2] = cosY * sinX;

    matrix[2][0] = sinZ * sinX + cosZ * cosX * sinY;    // Spalte 3
    matrix[2][1] = cosX * sinZ * sinY - cosZ * sinX;
    matrix[2][2] = cosY * cosX;

}
void MatrixZXY(float3 angles, float3x3& matrix)
{
    float cosX = cosf(angles.x);     // X
    float sinX = sinf(angles.x);

    float cosY = cosf(angles.y);     // Y
    float sinY = sinf(angles.y);

    float cosZ = cosf(angles.z);     // Z
    float sinZ = sinf(angles.z);


    matrix[0][0] = cosZ * cosY - sinZ * sinX * sinY;        // Spalte 1
    matrix[0][1] = cosY * sinZ + cosZ * sinX * sinY;
    matrix[0][2] = -cosX * sinY;

    matrix[1][0] = -cosX * sinZ;                            // Spalte 2
    matrix[1][1] = cosZ * cosX;
    matrix[1][2] = sinX;

    matrix[2][0] = cosZ * sinY + cosY * sinZ * sinX;        // Spalte 3
    matrix[2][1] = sinZ * sinY - cosZ * cosY * sinX;
    matrix[2][2] = cosX * cosY;

}


void PrintMatrix(string name, float3 angles, float3x3& matrix)
{
    cout << "-----------------------------" << endl;
    cout << name << "-Matrix" << endl;
    cout << "-----------------------------" << endl;

    cout << matrix[0][0] << " " << matrix[1][0] << " " << matrix[2][0] << " " << endl;
    cout << matrix[0][1] << " " << matrix[1][1] << " " << matrix[2][1] << " " << endl;
    cout << matrix[0][2] << " " << matrix[1][2] << " " << matrix[2][2] << " " << endl;
    cout << "-----------------------------" << endl << endl << endl;
}



void MatrixDecomposeYXZ(float3x3& matrix, float3& angles)
{
    angles.x = asinf(-matrix[2][1]);                        //              X
    if (cosf(angles.x) > 0.0001)                            // Not at poles X
    {
        angles.y = atan2f(matrix[2][0], matrix[2][2]);      //              Y
        angles.z = atan2f(matrix[0][1], matrix[1][1]);      //              Z
    }
    else
    {
        angles.y = 0.0f;                                    //              Y
        angles.z = atan2f(-matrix[1][0], matrix[0][0]);     //              Z
    }
}
Chris
  • 79
  • 1
  • 8
  • Please show and explain more of what you tried. It might help to give a code foundation which reads the data transforms them (not, i.e. just passes it through unchanged, i.e. makes a place holder for actual code) and outputs/forwards them in the needed form. If you have something which does the job and only fails in getting the rotation right, then make a [mcve] of it. – Yunnosch Jul 14 '19 at 08:42
  • Consider taking the [tour] and reading [ask], https://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/ https://codeblog.jonskeet.uk/2012/11/24/stack-overflow-question-checklist/ – Yunnosch Jul 14 '19 at 08:43
  • You must define the (1) the original coordinate system; (2) the target coordinate system; (3) the order of rotations. Then you can form your transformation matrix. See [Euler angles - Wikipedia](https://en.wikipedia.org/wiki/Euler_angles) as one example. There are roughly 4 different approaches to coordinate system choice and sequence of rotations. (wikipedia is good there too) – David C. Rankin Jul 14 '19 at 08:50
  • @DavidC.Rankin Hello David, thank you. THIS is an answer that really helps. I needed to know the steps to achieve my goal. Now i can maybe figure it out myself. – Chris Jul 14 '19 at 17:38
  • Any luck with the answer i posted? – Playdome.io Jul 16 '19 at 06:35

3 Answers3

1

Okay, the problem got solved on gamedev.net by Mr. Zakwayda (Thanks!) -> Link
Softimage XSI 2015 to Unreal Engine 4.22.3

void SpawnSMActor(const TCHAR *path,float sX,float sY,float sZ,float rX,float rY,float rZ,float pX,float pY,float pZ) 
{
    float rad = 0.0174532925199444; // FQuat needs Radians. So degree * Pi/180 | Pi/180 = 0.0174532...

    // Transform
    FVector Scale(sX, sY, sZ);      // Scale
    FVector Position;               // Translation

    // ************************************************************************************
    // Conversion XSI Coordinate System to UE4 Coordinate System

    // Position - Swap Z and Y axis and correct Position Scaling

    Position.X = pX * 100; 
    Position.Y = pZ * 100; 
    Position.Z = pY * 100; 

    // Quaternions - Convert Rotations from XSI to UE4

    FQuat qx(FVector(1, 0, 0), -rX * rad);
    FQuat qz(FVector(0, 0, 1), -rY * rad);
    FQuat qy(FVector(0, 1, 0), -rZ * rad);

    FQuat qu = qy * qz * qx; // Change Rotation Order if necessary

    FRotator Rotation(qu);
    FTransform Transform(Rotation, Position, Scale);

    // ************************************************************************************
    // Load Static Mesh from given Reference Path from UE4 Explorer

    UStaticMesh* StaMesh = LoadObject<UStaticMesh>(nullptr, path);

    // Creating the Actor and Positioning it in the World based on the Static Mesh
    UWorld* currentWorld = GEditor->GetEditorWorldContext().World();
    ULevel* currentLevel = currentWorld->GetCurrentLevel();
    UClass* StaticMeshClass = AStaticMeshActor::StaticClass();
    AActor* NewActorCreated = GEditor->AddActor(currentLevel, StaticMeshClass, Transform, true, RF_Public | RF_Standalone | RF_Transactional);
    AStaticMeshActor* smActor = Cast<AStaticMeshActor>(NewActorCreated);

    smActor->GetStaticMeshComponent()->SetStaticMesh(StaMesh);
    smActor->SetActorScale3D(Scale);

    // ID Name & Visible Name
    //smActor->Rename(TEXT("MyStaticMeshInTheWorld"));
    //smActor->SetActorLabel("MyStaticMeshInTheWorld");

    GEditor->EditorUpdateComponents();
    smActor->GetStaticMeshComponent()->RegisterComponentWithWorld(currentWorld);
    currentWorld->UpdateWorldComponents(true, false);
    smActor->RerunConstructionScripts();
    GLevelEditorModeTools().MapChangeNotify();
}
Chris
  • 79
  • 1
  • 8
1

I recently created a couple of converter functions between PlayCanvas(Unity has the same coordinate system) to Unreal Coordinate System. Too busy to write a C# implementation here, but hope this javascript one leads some poor souls in the right direction...

/** Convert spatial coordinates from PlayCanvas to Unreal **/
function Pos3D_PlayCanvasToUnreal(x, y, z) {
    return {x:-z*100, y:x*100, z:y*100};
}

/** Convert spatial quaternion from PlayCanvas to Unreal **/
function Quat_PlayCanvasToUnreal(q) {
    return {x:-q.z, y:q.x, z:q.y, w:-q.w};
}
Mann1ng
  • 43
  • 6
0

You can transform your rotation with FTransform in UE4. This provides you a transformation matrix and i believe might solve your problem.

https://api.unrealengine.com/INT/API/Runtime/Core/Math/FTransform/index.html

Transform composed of Scale, Rotation (as a quaternion), and Translation.

Transforms can be used to convert from one space to another, for example by transforming positions and directions from local space to world space.

And from the looks of it, to me it sounds like you just have to swap the Z and Y coordinate values, and the Pitch and Yaw values for the rotation.

Eg:

Pitch = Yaw;
Yaw = Pitch;

Here's a code example how to do this in UE4 C++

// We just simply swap the Z and Y Coordinates
FVector NewPosition;
NewPosition.X = InPosition.X;
NewPosition.Y = InPosition.Z;
NewPosition.Z = InPosition.Y;
SetActorLocation(NewPosition);

So we simply swapped the Y and Zcoordinates.

Now we can do the same with rotation note the naming is a little different but UE4 explains it pretty quick here's an image:

enter image description here

// We just simply swap the Pitch(Y) and Yaw(Z) angles
FRotator NewRotation;
NewRotation.Roll = InRotation.X;
NewRotation.Pitch = InRotation.Z;
NewRotation.Yaw = InRotation.Y;
SetActorRotation(NewRotation.Quaternion());

Now from your question i am not sure which direction the axes are but you can simply take the negative of the axes if the mesh is moving in the opposite direction for example:

FVector NewPosition;
NewPosition.X = -InPosition.X; // Note the - in front of InPosition.X; You can also multiply with -1
NewPosition.Y = InPosition.Z;
NewPosition.Z = InPosition.Y;
SetActorLocation(NewPosition);

Here we move InPosition.X by taking it's negative with -InPosition.X in the other direction. It should be that simple to convert between your 2 diffent coordinate systems.

Community
  • 1
  • 1
Playdome.io
  • 3,137
  • 2
  • 17
  • 34
  • Thank you for your answer. I looked into FTransform but couldnt find any code examples online.It also uses quaternions and to be honest iam a bit scared of them because i dont understand them. Next thing is that i would like to stay away from the UE4 API if possible (updates). Also iam not sure about pitch yaw and roll terminology. Iam 3D artist and used to XYZ.It also says that FTransform is for transforming local to worldspace or vice versa. Are you sure that this can also transform Righthanded Y-up XYZ Order to Lefthanded Z-up XZY order?Sorry iam very new to this stuff. – Chris Jul 16 '19 at 12:25
  • Yes, it can transform between 2 coordinate spaces, now it doesn't matter if it's relative or world or somerhing else. For your case i'll post a code sample. I don't understand why would be a problem using this api. It is very unlikely to change in the future. – Playdome.io Jul 16 '19 at 12:31
  • Also the pitch roll and yaw are equal to XYZ coordinates in degrees. And UE4 provides an api to use FRotators and Quaternions. – Playdome.io Jul 16 '19 at 12:32
  • What you have to do is very simple, just assign the X=X, Y=Z and Z=Y you can use blueprints or C++. In c++ it's FRotator what you want. – Playdome.io Jul 16 '19 at 12:33
  • Okay. You are right about the updates. I think this wont change. What i have is Euler angles from my 3d software. And i need Euler angles also for UE4. I have my whole code in C++ and not in blueprints. If its okay for you lets talk in XYZ. Its less confusing for me. Thanks you! – Chris Jul 16 '19 at 12:40
  • I've updated my answer with code samples please let me know if it helps – Playdome.io Jul 16 '19 at 12:41
  • @Chris the UE4 FRotatator uses Euler angles.;) There's a link about the FRotator https://forums.unrealengine.com/development-discussion/c-gameplay-programming/32158-rotator-angles-independent – Playdome.io Jul 16 '19 at 12:43
  • Okay. I see, the engine uses also Pitch Yaw and Roll. Haha. Thanks for the examples. I will dive into it and update my post. Thanks! :) – Chris Jul 16 '19 at 12:45
  • Not sure what direction your axes are, but that should be as simple flipping the angle by multiplying your input with `-1` to match the ue4's coordinate system's axes. Good luck and let me know – Playdome.io Jul 16 '19 at 12:46
  • Okay! Thanks! I will. – Chris Jul 16 '19 at 12:57
  • How do i get the bride now from "SetActorRotation(NewRotation.Quaternion());" to "FTransform objectTransform(objectRotation, objectPosition, objectScale);". I will investigate further. – Chris Jul 16 '19 at 14:17
  • You can call SetActorTransform if you're using transforms – Playdome.io Jul 16 '19 at 14:18
  • Okay, i made it work, but the Rotations are still off like before. I update my post. – Chris Jul 16 '19 at 15:12
  • Could you show in an another video whats happening now exactly? – Playdome.io Jul 16 '19 at 15:20
  • Ok i just saw the problem, you have ti negate your Z rotation as well. So `NewRotation.Pitch = -rZ;` – Playdome.io Jul 16 '19 at 15:23
  • You can see in the video that your rotation Pitch is negative while in UE4 it's positive – Playdome.io Jul 16 '19 at 15:23
  • I added a picture link. Need 4 more reputation to post actual pictures. – Chris Jul 16 '19 at 15:46
  • Did you try swapping up the two as i said? – Playdome.io Jul 16 '19 at 16:05
  • I mean do8ng -rZ on the pitch – Playdome.io Jul 16 '19 at 16:05
  • `NewRotation.Pitch = -rZ;` is also not working. The closest i can come is with `NewRotation.Yaw = -rY;` and the rest positive. But its still off. – Chris Jul 16 '19 at 16:09
  • Maybe i should just try to simulate it in Blueprint. I mean i know the input degrees and also the correct output degrees. – Chris Jul 16 '19 at 16:18