I'm following in the footsteps of this blog in implementing a transform hierarchy to my game engine scenes. I have a fairly similar implementation for a 3D transform:

typedef struct T3D
    Vector3 Position = { 0,0,0 };
    Quaternion Rotation = {0,0,0,1};
    Vector3 Scale = {1,1,1};
    T3D* Parent = nullptr;

And my objects/entities, have two of said transforms, one for world coordinates and local coordinates respectively.

T3D _locTransform = T3D{};      //Local space transform                     
T3D _glbTransform = T3D{};      //Global space transform

The pointer to the object's parent transform (unless it's an orphan/root object) is set through a simple accessor-type function:

virtual void SetParentTransform(Object* Parent){_locTransform.Parent = &Parent->_locTransform;}

Also each member of an object's local transform can be transformed and accessed in a similar manner:

virtual void SetTransform(T3D Trf) { _locTransform = Trf; }
virtual void SetPosition(Vector3 Pos) { _locTransform.Position = Pos; }
virtual void SetRotation(Quaternion Rot) { _locTransform.Rotation = Rot; }
virtual void SetScale(Vector3 Scl) { _locTransform.Scale = Scl; }

virtual void Translate(Vector3 Pos) { if (_static) return; _locTransform.Position = Vector3Add(_locTransform.Position, Pos); }
virtual void Rotate(Quaternion Rot) { if (_static) return; _locTransform.Rotation =  QuaternionMultiply(_locTransform.Rotation, Rot); }
virtual void Scale(Vector3 Scl) { if (_static) return; Scale = Vector3Add(_locTransform.Scale, Scl); }

A transform hierarchy/tree is formed with the relations between the .Parent members of the T3D struct. The objects also have a vector of pointers to their children, but that's not related to the transforms in any way.

I update the transforms of every object by calling this:

_glbTransform = TranslateTransform(_locTransform);

each frame.

I have a "scene tree" that is structured with said methods above as so:

/*  ├*/_monkeyParentActor   //At world position X:0, Y:0, Z:0
/*  │└─*/_monkeyActor       //At local position X:6, Y:0, Z:0
/*  │  ├─*/_monkeyRotator   //Irrelevant
/*  │  └─*/_monkeyShape     //At local position X:2, Y:0, Z:0

Note that the rotator object applies rotation to it's parent by calling the "Rotate" function of said parent described above. The shape object draws a mesh offset by it's own transform. The transform is handled exactly the same way as in the base object class, since It's inherits from it.

And as for the Implementation of TranslateTransform() I've tried a couple approaches:

Approach 1:

T3D TranslateTransform(const T3D& Trs)                  //TODO: Please make sure these works correctly and in the correct order

    T3D Translate;
    if (Trs.Parent != nullptr)
        Translate.Rotation = QuaternionMultiply(Trs.Parent->Rotation, Trs.Rotation);
        Translate.Position = Vector3RotateByQuaternion(Trs.Position, Trs.Parent->Rotation);
        Translate.Position = Vector3Add(Trs.Position, Trs.Parent->Position);
        Translate.Scale = Vector3Multiply(Trs.Scale, Trs.Parent->Scale);
        return Translate;
        return Trs;

What this results in, is instead of the result I'd expect from the positions given in the object tree written above, which should offset the MonkeyActor object 6-units left on the X-axis from the MonkeyParent object at the world origin (which it does), and then offset the MonkeyShape, which is a child of MonkeyActor again more to the left by another 4 units. Instead, I get the shape moving 4 units in the opposite direction, even though It's local transform's x-coordinate isn't negative. And in addition to this, the MonkeyActor rotates as it's supposed to. However, the MonkeyShape's position isn't offset at all by It's parent's rotation, It ends just staying at the same world coordinates and directly copying it's parent's rotation instead of "orbiting" the parent like expected:

enter image description here

Note how the local transform position given to the object doesn't respect the direction of the offset from it's parent.

Approach 2:

T3D TranslateTransform(const T3D& Trs)
    T3D Translate;
    if (Trs.Parent != nullptr)
        // Scale
        Translate.Scale = Vector3Multiply(Trs.Parent->Scale, Trs.Scale);

        // Rotation
        // Combine the rotations of the parent and the child
        Translate.Rotation = QuaternionMultiply(Trs.Parent->Rotation, Trs.Rotation);

        // Position
        // Rotate the local position offset of the child by the combined rotation
        Vector3 rotatedPosition = Vector3RotateByQuaternion(Trs.Position, Trs.Rotation);
        // Scale the rotated position offset by the parent's scale
        rotatedPosition = Vector3Multiply(rotatedPosition, Trs.Parent->Scale);
        // Add the scaled and rotated position offset to the parent's position
        Translate.Position = Vector3Add(rotatedPosition, Trs.Parent->Position);

        return Translate;
        return Trs;

With this, somewhat based on the blog mentioned in the beginning of this post. Instead of the MonkeyActor rotating on it's own axis and the MonkeyShape orbiting it at the distance of it's offset, I get the MonkeyActor orbiting it's parent: MonkeyParent and the MonkeyShape just staying in it's place copying it's parent's rotation:

enter image description here

It may not look like it from the gif, but the center of rotation for MonkeyActor is around It's parent MonkeyParent.

What would be the proper order of applying the transformations in a hierarchy like this in order for child transforms to keep their proper position relative to their parent's rotation? While also offsetting them by their local transform in the right direction? Am I missing something important here? I can provide additional information about my code if needed.

  • \$\begingroup\$ Since this might be too broad of a topic for a gamedev question, could somebody with sufficient privileges please move this question over to StackOverflow? \$\endgroup\$
    – Boyfinn
    Feb 1 at 13:37


You must log in to answer this question.

Browse other questions tagged .