Reputation:
I have an OpenGL C# project, that I would like to give functionality like Unity3D game engine.
Introduction: I have Transform class that provides transformations matrix to shader. Each transform can have parent transform. Code that calculates final transformations matrix looks like this:
public Vector3 LocalPosition { get; set; }
public Quaternion LocalRotation { get; set; }
public Vector3 LocalScale { get; set; }
public Matrix GetModelMatrix() {
Matrix result;
if(HasParent)
result = Parent.GetModelMatrix();
else
result = Matrix.CreateIdentity();
ApplyLocalTransformations(result);
return result;
}
private void ApplyLocalTransform(Matrix matrix)
{
matrix.Translate(LocalPosition);
matrix.Rotate(LocalRotation);
matrix.Scale(LocalScale);
}
As you see LocalPosition, LocalScale and LocalRotation are transformations RELATIVE to parent. This code works fine.
Problem: I want to add 3 more properties (hello Unity3D):
public Vector3 AbsolutePosition { get; set; }
public Quaternion AbsoluteRotation { get; set; }
public Vector3 AbsoluteScale { get; set; }
I want to have ability to get and set absolute transformations to child transforms. While setting Absolute values Local should be updated consistently and vice versa.
Example: We have parent at position (1, 1, 1) and child with LocalPosition = (0, 0, 0), having this information we can calculate child's AbsolutePosition = (1, 1, 1). Now we set child's AbsolutePosition = (0, 0, 0). It's LocalPosition will now be = (-1, -1, -1). It's a very simple example, in real scenario we have to consider parent's scale and rotation to calculate Position.
How to calculate Absolute and Local Position i have an idea: I can take last column from transformations matrix and it will be my AbsolutePosition. To get LocalPosition i can subtract from AbsolutePosition last column of parent transformations matrix. But mathematics behind Rotation and Scale still unclear for me.
Question: Can you help me with algorithm that will calculate Local and Absolute Position, Rotation and Scale?
P.S.: considering performance would be great.
Upvotes: 2
Views: 2824
Reputation: 1519
I have dealt with this exact problem. There is more than one way to solve it and so I will just give you the solution that I came up with. In short, I store the position, rotation and scale in both local and world coordinates. I then calculate deltas so that I can apply changes made in one coordinate space to the other.
Finally, I use events to broadcast the deltas to all descended game objects. Events are not strictly necessary. You could just recursively call some functions on the transform components of descended game objects in order to apply the deltas down the game object tree.
It's probably best to give an example at this point, so take a look at this setter method for the transform's local position (which I have lifted from a very small game that I worked on):
void Transform::localPosition(const Vector3& localPosition)
{
const Vector3 delta = localPosition - m_localPosition;
m_localPosition = localPosition; // Set local position.
m_position += delta; // Update world position.
// Now broadcast the delta to all descended game objects.
}
So that was trivial. The setter for the world position is similar:
void Transform::position(const Vector3& position)
{
const Vector3 delta = position - m_position;
m_position = position; // Set world position.
m_localPosition += delta; // Update local position.
// Now broadcast the delta to all descended game objects.
}
The principle is the same for rotation:
void Transform::localRotation(const Quaternion& localRotation)
{
const Quaternion delta = m_localRotation.inverse() * localRotation;
m_localRotation = localRotation; // Set the local orientation.
m_rotation = delta * m_rotation; // Update the world orientation.
// Now broadcast the delta to all descended game objects.
}
void Transform::rotation(const Quaternion& rotation)
{
const Quaternion delta = m_rotation.inverse() * rotation;
m_rotation = rotation; // Set the world orientation.
m_localRotation = delta * m_localRotation; // Update the local orientation.
// Now broadcast the delta to all descended game objects.
}
And finally scale:
void Transform::localScale(const Vector3& scale)
{
const Vector3 delta = scale - m_localScale;
m_localScale = scale; // Set the local scale.
m_scale += delta; // Update the world scale.
// Now broadcast the delta to all descended game objects.
}
void Transform::scale(const Vector3& scale)
{
const Vector3 delta = scale - m_scale;
m_scale = scale; // Set the world scale.
m_localScale += delta; // Update the local scale.
// Now broadcast the delta to all descended game objects.
}
I'm not sure how you could improve on this from a performance perspective. Computing and applying deltas is relatively cheap (certainly much cheaper than decomposing transformation matrices).
Finally, since you are attempting to emulate Unity, you might want to take a look at my small c++ mathematics library, which is modeled on Unity's maths classes.
So I left out quite a few details in my original answer which seems to have caused some confusion. I provide below a detailed example that follows the concept of using deltas (as described above) in response to Xorza's comment.
I have a game object that has one child. I will refer to these game objects as parent and child respectively. They both have default scale (1, 1, 1) and are positioned at the origin (0, 0, 0).
Note that Unity's Transform class does not allow writing to the lossyScale
(world scale) property. So, following the behavior provided by Unity, I will deal with modifications to the parent transform's localScale
property.
Firstly, I call parent.transform.setLocalScale(0.1, 0.1, 0.1)
.
The setLocalScale
function writes the new value to the localScale
field and then calculates the scaling delta as follows:
scalingDelta = newLocalScale / oldLocalScale
= (0.1, 0.1, 0.1) / (1, 1, 1)
= (0.1, 0.1, 0.1)
We use this scaling delta to update the transform's world scale
property.
scale = scalingDelta * scale;
Now, because changes to the parent's transform properties (local or world) affect the child transform's world properties, I need to update the child transform's world properties. In particular, I need to update the child transform's scale
and position
properties (rotation is not affected in this particular operation). We can do this as follows:
child.transform.scale = scalingDelta * child.transform.scale
= (0.1, 0.1, 0.1) * (1, 1, 1)
= (0.1, 0.1, 0.1)
child.transform.position = parent.transform.position + scalingDelta * child.transform.localPosition
= (child.transform.position - child.transform.localPosition) + scalingDelta * child.transform.localPosition
= ((0, 0, 0) - (0, 0, 0)) + (0.1, 0.1, 0.1) * (0, 0, 0)
= (0, 0, 0)
Note that accessing the parent transform's position is difficult if you use events to pass the deltas down the game object tree. However, since child.transform.position = parent.transforn.position + child.transform.localPosition
, we can compute the parent transform's world position from the child transform's world position and local position.
Also, importantly, note that the child transform's local properties are not changed.
Secondly, I call child.transform.setPosition(1, 1, 1)
.
The setPosition
function writes the new value to position
and then calculates the translation delta as follows:
translationDelta = newPosition - oldPosition
= (1, 1, 1) - (0, 0, 0)
= (1, 1, 1)
Finally, the setPosition
function updates the transform's localPosition
using the computed delta. However, note that the computed translation delta is in world space coordinates. So we need to do a little work to convert it into local space coordinates before updating localPosition
. In particular, we need to take into account the parent transform's world scale.
localPosition = localPosition + translationDelta / parent.transform.scale
= localPosition + translationDelta / (scale / localScale)
= localPosition + translationDelta * (localScale / scale)
= (0, 0, 0) + (1, 1, 1) * ((1, 1, 1,) / (0.1, 0.1, 0.1))
= (10, 10, 10)
Again, it is not necessary to look up the parent transform's world scale. This can be calculated from the child transform's world scale and local scale.
In this example I dealt with changes to the parent transform's scale. The same principles apply for changes to the parent's position and rotation, although the calculations will be different.
Upvotes: 0