World Space Setters

The components of each transform are set realtive to the parent of the transform. Needing to set global / world space values for these components is a fairly common task. To achieve this we need to figure out what the local values would be if the requested global components where set, then assign those as the local components.

Setting the global components of a transform generally follow the same formula. Find the parent's global transform representation, invert it and multiply the desired global transform by the inverted parent transform. This effectivley moves whatever component is being set from global space to the local space of the parent, any transformation that's left is local to the parent.

Setting Global Rotation

Setting the global rotation of a transform is trivial, first find the world space rotation of the transforms parent and invert it. Next move the desired global rotation into the local space of the parent transform by multiplying the inverse of the parents rotation. Assign the result of this multiplication as the local rotation of the transform.

void SetGlobalRotation(Transform t, Quaternion rotation)  {
    if (t.parent == NULL) {
        t.rotation = rotation;
        return
    }

    Quaternion parentGlobal = GetGlobalRotation(t.parent)
    Quaternion invParentGlobal = Inverse(parentGlobal);

    t.rotation = invParentGlobal * rotation;
}

Setting Global Position

To set the global position of a transform, we need to take the global point and transform it by the inverse of the parent transform (If there is one). We could either find the matrix representation of the global parent transform, invert it and multiply the point by that OR we can do it component wise, like the global position getter. Doing it component wise will save a few multiplications.

To invert a point by a transform, the inverse of each component needs to be applied, in reverse order. Normally you'd transform a point by applying it's scale, rotation then position. To apply the inverse of a transform, apply the inverted position, inverted rotation, then inverted scale.

That was a very long winded explanation, the code is much simpler:

Vector3 InverseTransformPoint(Transform t, Vector3 point) {
    // Recursive function, apply inverse of parent transform first
    if (t.parent != NULL) {
        point = InverseTransformPoint(t.parent, point)
    }

    // First, apply the inverse translation of the transform
    point = point - t.position;

    // Next, apply the inverse rotation of the transform
    Quaternion invRot = Inverse(t.rotation);
    point = point * invRot;

    // Finally, apply the inverse scale
    point = point / t.scale; // Component wise vector division

    return point
}

Once we have a function for applying the inverse of a transform to a point, setting the global position of a transform is trivial.

void SetGlobalPosition(Transform t, Vector3 position) {
    if (t.parent != NULL) {
        pos = InverseTransformPoint(t.parent, position);
    }

    t.position = position;
}

Setting Global Scale

Working with global scale is probably the most complicated part of working with a transform hierarchy. Depending on the global basis vectors of the transform, it may contain skew data, not just scale. Because of this, we can't just set global scale. We have to set the entire 3x3 sub matrix where scale data may live.

To set this matrix we need to find the global rotation and scale matrix of the transform, where the scale of this transform isn't taken into account but the scale of all parent transforms is. Invert this matrix, and mulyiply the desired 3x3 matrix by the inverted matrix. The main diagonal of this matrix is the new scale.

void SetGlobalScaleFromRotationScaleMatrix(Transform t, Matrix3 rsMat) {
    // Reset scale to 1, do this so we can get the  global rotation and scale 
    // without the scale of this transform but with the scale of all parents
    t.scale = Vector3(1, 1, 1); 

    // Find inverse global matrix (scale of all parent tranforms, not this one)
    Matrix3 globalRS = GetGlobalRotationAndScale(t);
    Matrix3 inverseRS = Inverse(globalRS);

    // Bring the rotation scale matrix into local space
    Matrix3 localRS = inverseRS * rsMat;
    
    // Main diagonal is the new scale
    t.scale = Vector3(localRS[0], localRS[4], localRS[8]);
}

Setting global scale with a 3x3 matrix that also contains rotation data is un-intuitive at best. We can make a simple wrapper function that takes the desired global scaling vector as an argument and constructs the rotation-scale matrix using the current global rotation of the transform:

void SetGlobalScale(Transform t, Vector3 scale) {
    Quaternion globalRotation = GetGlobalRotation(t)

    var x = Vector3(scale.x, 0, 0) * globalRotation;
    var y = Vector3(0, scale.y, 0) * globalRotation;
    var z = Vector3(0, 0, scale.z) * globalRotation;
    
    Matrix3 rotationAndScaleMat = Matrix3(
        x.x, x.y, x.z,
        y.x, y.y, y.z,
        z.x, z.y, z.z
    )

    SetGlobalScaleFromRotationScaleMatrix(t, rotationAndScaleMat);
}