How Group Scale and Rotation Affects Children in Three.js

In Three.js, grouping objects using THREE.Group or THREE.Object3D allows you to manipulate multiple meshes as a single unit. When you scale or rotate a parent Group, these transformations are automatically propagated to all child objects through hierarchical matrix multiplication, altering their world space position, rotation, and size while preserving their local transformation properties.

The Mechanism: Matrix Inheritance

Three.js uses a hierarchical scene graph where every 3D object inherits transformations from its parent. Each object has two primary matrices: * matrix: The local transformation matrix (relative to its parent). * matrixWorld: The global transformation matrix (relative to the scene origin).

When you modify the rotation or scale of a parent Group, the child’s local properties (child.rotation, child.scale, and child.position) remain completely unchanged. However, during the rendering loop, Three.js updates the child’s matrixWorld by multiplying the parent’s matrixWorld with the child’s local matrix:

\[\text{child.matrixWorld} = \text{parent.matrixWorld} \times \text{child.matrix}\]

This mathematical relationship dictates exactly how rotation and scaling behave.

What Happens During Parent Rotation?

When you rotate a parent Group:

  1. Pivot Point Alignment: The child objects rotate around the parent Group’s origin \((0, 0, 0)\), not their own individual origins. The parent’s origin acts as the pivot point for the entire group.
  2. World Position Update: Unless a child is positioned exactly at \((0, 0, 0)\) in local space, its world position will change as it orbits the parent’s origin.
  3. World Rotation Update: The physical orientation of the child in the 3D scene changes. To retrieve the actual rotation of a child in world space, you must use child.getWorldQuaternion() or child.getWorldDirection(), as the child.rotation property will only show its local offset.

What Happens During Parent Scaling?

When you scale a parent Group:

  1. Relative Distance Scaling: The distance between the child objects and the parent’s origin scales proportionally. If you double the scale of the parent, a child positioned at local coordinate \((2, 0, 0)\) will now be at world coordinate \((4, 0, 0)\).
  2. Visual Size Scaling: The visual size of the child objects scales according to the parent’s scale factors.
  3. Local vs. World Scale: The child.scale property remains unchanged (e.g., \((1, 1, 1)\)), but its absolute size in the scene is modified. To find the actual visual scale, you must query child.getWorldScale().

Important Caveat: Non-Uniform Scaling

Applying non-uniform scaling (different values for X, Y, and Z) to a parent Group can introduce rendering issues. If a parent group is scaled non-uniformly (e.g., group.scale.set(2, 1, 1)) and a child object inside that group is rotated, the child will experience shear or skewing distortion.

To avoid unwanted warping of child geometries, always try to use uniform scaling (group.scale.set(2, 2, 2)) when working with grouped objects that undergo independent rotations.