Parent-Child Relationships

Building hierarchies with Object3D

Understanding Object Hierarchies

In Three.js, every object (meshes, lights, cameras, groups) inherits from Object3D. This base class provides a powerful feature: parent-child relationships. When you add an object as a child of another, the child's transformations (position, rotation, scale) become relative to its parent.

What does this mean? Well, it boils down to allowing you to manipulate the parents and having the manipulation affect the children, too. If you spin a parent, the children spin. If you move a parent, the children move, too. Because children can have children (really, there's no practical limit here), you can create really beautiful, complex effects with little effort. Stuff that would be a major headache to animate by hand become trivial.

Starting with adding children

Use the .add() method to create a hierarchy:

typescript
// Create a parent and child
const parent = new THREE.Mesh(parentGeometry, parentMaterial);
const child = new THREE.Mesh(childGeometry, childMaterial);

// Make child a child of parent
parent.add(child);

// Add parent to scene (child comes along automatically)
scene.add(parent);

// Position child relative to parent
child.position.x = 2; // 2 units to the right of parent's center

Transform Inheritance

The key insight is that children inherit their parent's transformations. Watch the demo below - the small cubes are children of the larger cube. When the parent rotates, the children orbit around it while also spinning on their own axes:

typescript
// Parent rotates
parent.rotation.y += 0.01;

// Children rotate on their own axis
// But they ALSO move with the parent's rotation
children.forEach(child => {
  child.rotation.x += 0.02;
});

This is incredibly useful because you don't need to calculate complex orbital paths - you just rotate the parent and the children follow.

Local vs World Coordinates

When you set child.position.x = 2, that's in local space (relative to the parent). To get the actual position in the scene (world space), use:

typescript
// Get world position
const worldPosition = new THREE.Vector3();
child.getWorldPosition(worldPosition);

// Get world quaternion (rotation)
const worldQuaternion = new THREE.Quaternion();
child.getWorldQuaternion(worldQuaternion);

// Get world scale
const worldScale = new THREE.Vector3();
child.getWorldScale(worldScale);

Using Groups

THREE.Group is an empty container specifically designed for organizing objects. It's like an invisible parent that lets you transform multiple objects together:

typescript
// Create a group
const group = new THREE.Group();

// Add multiple objects to the group
group.add(cube1);
group.add(cube2);
group.add(cube3);

// Now transform them all at once
group.position.y = 1;
group.rotation.z = Math.PI / 4;
group.scale.set(1.5, 1.5, 1.5);

// Add group to scene
scene.add(group);

Practical Example: A Solar System

Parent-child relationships make orbital systems trivial to implement. Each body orbits its parent without complex math:

typescript
// Sun at center (added to scene)
const sun = new THREE.Mesh(sunGeometry, sunMaterial);
scene.add(sun);

// Earth orbits sun
const earthPivot = new THREE.Group(); // invisible pivot point
sun.add(earthPivot);
const earth = new THREE.Mesh(earthGeometry, earthMaterial);
earth.position.x = 5; // distance from sun
earthPivot.add(earth);

// Moon orbits earth
const moonPivot = new THREE.Group();
earth.add(moonPivot);
const moon = new THREE.Mesh(moonGeometry, moonMaterial);
moon.position.x = 1; // distance from earth
moonPivot.add(moon);

// Animation - just rotate the pivots!
function animate() {
  earthPivot.rotation.y += 0.01;  // Earth orbits sun
  moonPivot.rotation.y += 0.03;   // Moon orbits earth (faster)
  earth.rotation.y += 0.02;       // Earth spins
}

Removing Children

You can remove objects from their parent and optionally add them elsewhere:

typescript
// Remove from parent
parent.remove(child);

// Remove from scene entirely
scene.remove(object);

// Move to different parent
oldParent.remove(child);
newParent.add(child);

// Clear all children
parent.clear();

Traversing the Hierarchy

You can walk through all descendants of an object:

typescript
// Visit every descendant
parent.traverse((object) => {
  console.log(object.name);

  // Common pattern: find all meshes
  if (object.isMesh) {
    object.material.wireframe = true;
  }
});

// Only visit visible descendants
parent.traverseVisible((object) => {
  // ...
});

// Get direct children only
parent.children.forEach((child) => {
  // ...
});
←   The RendererPosition, Rotation, and Scale   →