Parent transforming issue (OpenGL & GLM)

Started by
5 comments, last by JoeJ 3 years, 8 months ago

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!

Advertisement

I need to ask this: what do you expect after aplying parent to an object, its not clear to me what result you would expect, i may only wonder that you do not move childs to pivot point.

Thanks for the reply. I want the result I get from Unity in the same situation. I'm not really asking for a solution, I'm just wondering why I get different results like posted above. Once I attach, the rotation and scale go a little bit crazy (different from Unity results). At least, rotation should be -45º, not -63.4º. Also what do you mean by moving children to pivot point? I don't modify the pivot of the objects for now, by default, the pivot is in the feet.​ The transform is based on the pivot, I don't use any custom-made origins like center of the geometry or anything.

. I want the result I get from Unity in the same situation - LOL RIGHT ISNT IT OBVIOUS?

Anyway what is scale for?

How do you expect ttansformation of object to be valid just by multiplying their matrices, there are at least 4 issues going on here but either none of them may be the problem.

If objects 1 height is scaled by 2 and tge object is rotated by 30 degrees what do you expect that object 2 will have? The same transformation? How do you think scaling now comes in place for that.

Even when you decompose all ur matrices of parent you dont apply everything just by adding values and recomouting matrices

Once you rotate object 2 by obj 1 transformation you need to move it to predefined pivot anyway i may look like an idiot here but from my xp putting scale rot and translation in one matrix affects evertyhing applying scale scales transformation (rotation)

The ebst way would be to keep transformations separated.

Anyway: object 1

Move to geometric center of sprite

scale to define its size

Move to scaled pivot and.rotate

Translate

Object 2 now i dont know what you expect to obj 2 be after applying transformation and scaling but you scale it by parent then by your val, move to scaled pivot, rotate by parent rot and by your obj rot

Translate

Just to add you operate on local actual object space

I just came with the idea of wrong normalization routine, think of it if you pitch vector and then try to only update x, z (yaw) => this means after pitch, you already have a length in a vector then you add x z values to it and you try to normalize it, but think further, if you extend it to infinity you get infinite inprecision…

Did not read it all, but looks your problem comes from nonuniform scale, which leads to skewed, non ortho normal child frames if not treated individually.

I don't know how Unity handles this, but most 3D modeling software does not propagate skewed matrices down to the children. Instead they propagate only translation and rotation and make a scaled copy from the result, also the translation and scale of children is affected from parent scaling. This is usually preferable in practice, but the downside is you can scale only along local XYZ axis individually, but you can not rotate, scale X, rotate back to generate a skew transform on purpose.

In fact the only Application i've ever seen that uses trivial propagation of skewed matrices was Adobe After effects, and as a result you really try to avoid nonuniform scale to avoid total mess.

But i's a difficult topic without an obvious best solution. In my editor i replicated the scaling i perceived as ‘standard’ like this:

class SceneGraphNode 
	{
	public:

		SRT srt; // scale, rotate and translate values (user editable)
		matrix toWorld; // worldspace; Orientation & Scale only
		matrix toWorldScaled; // ...scaling added
		matrix fromWorldScaled; // ...inverse
		vec scale;
 // each node tracks this seperately

		void UpdateFromSrt (SceneNode *parent)
 // this is called while traversing the hierarchy starting from the root to transform the whole tree
		{
			if (!parent) return;
			
			// make local matrix from SRT values
			matrix local;
			SRT srt = this->srt;
			srt.translation[0] *= parent->scale[0];
			srt.translation[1] *= parent->scale[1];
			srt.translation[2] *= parent->scale[2];
			srt.CalcRTMatrix (local);		


			// make worldspace matrix
			toWorld = parent->toWorld * local;
			
			// multiply current with parent scale
			scale = parent->scale;
		
			scale[0] *= srt.scale[0];
			scale[1] *= srt.scale[1];
			scale[2] *= srt.scale[2];	
			
			
			
// make a scaled copy of the world matrix, which is used only to render node content but won't affect child nodes
			toWorldScaled = toWorld;
			toWorldScaled[0] *= scale[0];
			toWorldScaled[1] *= scale[1];
			toWorldScaled[2] *= scale[2];

			fromWorldScaled = toWorldScaled.Inversed();
		}
	}; 

It behaves as expected from experience with other software.

But notice scaling x will scale child x too, even if child is rotated so it's x axis differs from parent x.

So i'm not really lucky with it, and i miss the option to skew things.

I'm actually thinking about giving options per node so the user can choose what option he prefers…

This topic is closed to new replies.

Advertisement