Hi, I'm implementing a parenting system in my engine and I'm having an issue that is confusing me at this point. Not going to lie, I'm not expert in matrix transformation, I just know how to do the operations, the proper order etc. Not advanced stuff although I don't think my problem is related to “advanced” knowledge either.
Basically everything works, the issue pops out when the parent has a scale ≠ (1, 1, 1) AND rotation ≠ basic trigonometric ratio (0, 90, 180 and 270), let's call this situation A.
I have 3 objects, obj 1, obj 2 and obj 3, my testing order is obj 2 attaches to obj 1 so obj 1 becomes the parent of obj 2 and obj 3 attaches to 2. I changed the order of the attachments and the results are correct if it's not a situation like A (the one I mentioned before). Here's an example of a situation ≠ A.
Before parenting:
· Obj 1 transformation: T(0, 2.5, 0), R(45, 0, 0), S(1, 1, 1).
· Obj 2 transformation: T(0, 5, 0), R(0, 0, 0), S(1, 1, 1).
· Obj 3 transformation: T(0, 7.5, 0), R(0, 0, 0), S(1, 1, 1).
After parenting (obj 1 parent of obj 2 parent of obj 3). The following transforms are displayed in local space, it's more UI friendly.
RQ = Rotation Quaternion.
As you can see everything is done properly. This is correct as long as it's not a situation like A. The problem comes when it's a situation A. This is an example of such situation.
As you can see here, obj 2 and 3 are deformed in a bad way. The first time I saw this I went to Unity Engine to check if I had the same results, the results are similar but still not correct, I don't know if it's because of Unity doing some engine-specific magic or something I'm doing wrong. The difference is not huge but it's still wrong I assume. I think it's due to some post-processing of the scale and rotation. I will explain how I do my transformations now.
Each object contains a transform instance. The transform class contains these:
vec3 position { 0.f, 0.f, 0.f },
euler_rotation { 0.f, 0.f, 0.f },
scl { 1.f, 1.f, 1.f };
quat rotation { 1.f, 0.f, 0.f, 0.f };
mat4 local_matrix = mat4(1.f),
rotation_matrix = mat4(1.f),
matrix = mat4(1.f);
When rendering the objects I just multiply the parent world matrix with the object local matrix like this:
void Transform::update()
{
if (auto parent = owner->get_parent())
matrix = parent->get_transform()->get_matrix() * local_matrix;
else matrix = local_matrix;
}
The multiplication should be fine since OGL uses a left-handed coordinates system. ("matrix" member is the matrix that will be uploaded to shaders to render the object, which is always the world space transformation matrix).
And now the important part, the parenting.
vec3 scale;
quat rotation;
vec3 position;
vec3 skew;
vec4 perspective;
auto local_matrix = glm::inverse(parent->transform->get_matrix()) * transform->get_local_matrix(); // the parent "matrix" is the world space matrix of that parent
glm::decompose(local_matrix, scale, rotation, position, skew, perspective);
// some debug prints of position, rotation and scale here
transform->set_position(position);
transform->set_rotation(rotation);
transform->set_scale(scale);
//transform->set_local_matrix(local_matrix);
This is the Entity::set_parent function which sets its transform to the new one relative to the parent. It's important to note that if I comment the set_position, set_rotation and set_scale function and I just set the local matrix instead, the objects won't move, rotate or scale, in other words, the transformation is kept after parenting but I need to decompose the matrix to update the individual members. The 3 functions to apply position, rotation and scale just changes the members of the transform instance and updates the matrix like this:
void Transform::update_local_matrix()
{
local_matrix = glm::scale(glm::translate(mat4(1.f), position) * rotation_matrix, scl);
}
void Transform::set_position(const vec3& val)
{
position = val;
update_local_matrix();
}
void Transform::set_rotation(const quat& val)
{
rotation = val;
euler_rotation = glm::eulerAngles(val);
rotation_matrix = mat4(val);
update_local_matrix();
}
void Transform::set_scale(const vec3& val)
{
scl = val;
update_local_matrix();
}
I think this has something to do with the “skew” when doing the composition since this skew vector is ≠ (0, 0, 0) when it's a non-A situation, either something's not right when doing the decomposition or I'm missing more post-processing after the decomposition. My goal is to make it work like Unity for learning purposes (I guess this is how engines do it anyways, so basically, the correct way of doing it). I hope I explained myself and I think I posted enough information, if not, ask for it, I will gladly provide it. I hope someone knows what's going on because I'm worn out by googling away and looking everywhere for similar issues. Thanks in advance!